Changeset bbf9d1a in Klonkt
- Timestamp:
- 06/21/2026 02:32:39 PM (3 months ago)
- Branches:
- main
- Children:
- 4885eab
- Parents:
- 929d98d
- Location:
- src
- Files:
-
- 17 edited
-
services/i18n.js (modified) (3 diffs)
-
views/pages/admin-audio.ejs (modified) (23 diffs)
-
views/pages/admin-circle.ejs (modified) (4 diffs)
-
views/pages/admin-comments.ejs (modified) (5 diffs)
-
views/pages/admin-epk.ejs (modified) (3 diffs)
-
views/pages/admin-google.ejs (modified) (2 diffs)
-
views/pages/admin-help.ejs (modified) (2 diffs)
-
views/pages/admin-newsletter.ejs (modified) (1 diff)
-
views/pages/admin-playlists.ejs (modified) (7 diffs)
-
views/pages/admin-seo.ejs (modified) (4 diffs)
-
views/pages/admin-settings.ejs (modified) (13 diffs)
-
views/pages/admin-shows.ejs (modified) (4 diffs)
-
views/pages/admin-site-edit.ejs (modified) (12 diffs)
-
views/pages/admin-sites.ejs (modified) (7 diffs)
-
views/pages/admin-stats.ejs (modified) (7 diffs)
-
views/pages/admin-updates.ejs (modified) (5 diffs)
-
views/pages/admin-users.ejs (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/i18n.js
r929d98d rbbf9d1a 110 110 'search.empty': 'Niets gevonden.', 'search.in_post': 'in post →', 111 111 'like.login_title': 'Log in om deze post te liken', 112 // === Beheer-sub-pagina's === 113 'aset.title': 'Instellingen', 114 'aset.back_admin': 'Beheer', 115 'aset.mode': 'Modus', 116 'aset.mode_help': 'Bepaalt hoe deze installatie werkt. Wisselen is veilig: er wordt niets verwijderd — Solo verbergt alleen de multi-onderdelen en toont je primaire site.', 117 'aset.solo': 'Solo', 118 'aset.solo_title': 'één site (de jouwe).', 119 'aset.solo_desc': 'Geen gebruikers-directory, geen site-wissel. Bezoekers kunnen wel met Google inloggen om te reageren.', 120 'aset.hub': 'Hub', 121 'aset.hub_title': 'bedrijfssite met', 122 'aset.premium_badge': 'premium', 123 'aset.hub_desc': 'Jij (admin) wijst gebruikers een eigen Klonkt Hub toe. Gesloten cirkel: geen open registratie.', 124 'aset.hub_locked_note': 'Vereist premium — koppel Patreon in deze pagina.', 125 'aset.circle': 'Cirkels', 126 'aset.circle_title': 'solo + federatie.', 127 'aset.circle_desc': 'Eén eigen site die de publieke posts van andere Klonkt-sites toont. Asymmetrisch: jij bepaalt wie in jouw cirkel zit.', 128 'aset.save': 'Opslaan', 129 'aset.hub_home': 'Hub-hoofdpagina', 130 'aset.hub_home_help_1': 'De openbare hub-pagina', 131 'aset.hub_home_help_2': 'is generiek — van geen enkele gebruiker. Hier stel je de naam en intro in. Jouw eigen Klonkt Hub blijft los bestaan onder', 132 'aset.name': 'Naam', 133 'aset.name_ph': 'bijv. Studio Noord', 134 'aset.tagline': 'Tagline', 135 'aset.tagline_ph': 'bijv. Onafhankelijk muzieklabel', 136 'aset.intro': 'Intro', 137 'aset.intro_ph': 'Korte introtekst onder de titel.', 138 'aset.hero_image': 'Hero-afbeelding (URL)', 139 'aset.hero_image_hint': 'optioneel; achtergrond van de hero', 140 'aset.hero_upload': '…of upload een afbeelding', 141 'aset.hero_upload_hint': 'jpg/png/webp/gif, max 5 MB; vervangt de URL hierboven', 142 'aset.hero_overlay': 'Donkere overlay', 143 'aset.hero_overlay_hint': 'maakt de hero donkerder zodat de tekst leesbaar blijft', 144 'aset.preview_overlay': 'Voorbeeld (met overlay):', 145 'aset.hero_preview_alt': 'Hero-voorbeeld', 146 'aset.your_circle': 'Je cirkel', 147 'aset.your_circle_help': 'Beheer welke andere Klonkt-sites je in je cirkel toont, en of jouw site in cirkels van anderen mag verschijnen. Asymmetrisch: jij bepaalt wie je volgt.', 148 'aset.manage_circle': 'Beheer je cirkel', 149 'aset.premium': 'Premium (Patreon)', 150 'aset.premium_help_1': 'Ontgrendel de premium-modules (Hub-modus, Statistieken, Fan-login, Prutter-DMs) met je', 151 'aset.premium_lifetime': '$10-lifetime', 152 'aset.premium_help_2': 'Patreon-steun. De app en alle updates blijven gratis.', 153 'aset.premium_active': 'Premium actief.', 154 'aset.lifetime_support': 'Lifetime-steun:', 155 'aset.patreon_disconnect': 'Patreon ontkoppelen', 156 'aset.patreon_no_lifetime': 'Patreon gekoppeld, maar nog geen $10 lifetime', 157 'aset.now': 'nu', 158 'aset.patreon_support_again': 'Steun de campagne en koppel opnieuw.', 159 'aset.patreon_reconnect': 'Opnieuw koppelen', 160 'aset.patreon_not_connected': 'Nog niet gekoppeld.', 161 'aset.patreon_connect': 'Koppel Patreon', 162 'aset.google_login': 'Google-login (bezoekers)', 163 'aset.google_login_help': 'Laat bezoekers met Google inloggen om te reageren — met je eigen Google Cloud-client.', 164 'aset.status_set': 'Status: ingesteld', 165 'aset.not_set_yet': 'Nog niet ingesteld.', 166 'aset.google_login_setup': 'Google-login instellen', 167 'aset.newsletter': 'Nieuwsbrief', 168 'aset.newsletter_help_1': 'Toon een', 169 'aset.newsletter_footer_field': 'aanmeldveld in de footer', 170 'aset.newsletter_help_2': 'van je site zodat bezoekers zich op elke pagina kunnen inschrijven. (De volledige aanmeldpagina blijft op', 171 'aset.newsletter_show_footer': 'Aanmeldveld in de footer tonen', 172 'aset.email_smtp': 'E-mail (SMTP)', 173 'aset.smtp_help_1': 'Nodig om de', 174 'aset.smtp_help_newsletter': 'nieuwsbrief te versturen', 175 'aset.smtp_help_2': ',', 176 'aset.smtp_help_notify': 'show-notify', 177 'aset.smtp_help_3': '-mails te sturen en wachtwoord-reset per mail te laten werken. Vul de gegevens van je mailprovider in (bv. je hostingmail, Gmail-app-wachtwoord, Brevo, Mailgun…).', 178 'aset.via_env': 'via .env', 179 'aset.smtp_not_set': 'Nog niet ingesteld — versturen werkt nog niet.', 180 'aset.smtp_host': 'SMTP-host', 181 'aset.smtp_port': 'Poort', 182 'aset.smtp_port_hint': '587 (STARTTLS) of 465 (SSL)', 183 'aset.smtp_user': 'Gebruikersnaam', 184 'aset.smtp_pass': 'Wachtwoord', 185 'aset.smtp_pass_set_hint': 'ingesteld; leeg laten = ongewijzigd', 186 'aset.smtp_pass_ph_set': '•••••••• (ingesteld)', 187 'aset.smtp_pass_ph': 'app-wachtwoord', 188 'aset.smtp_from': 'Afzender', 189 'aset.smtp_from_hint': 'optioneel; standaard = gebruikersnaam', 190 'aset.smtp_from_ph': 'Jouw Naam <jij@jouwprovider.nl>', 191 'aset.smtp_save': 'SMTP opslaan', 192 'aset.clear': 'Wissen', 193 'aset.test_mail_to': 'Testmail sturen naar', 194 'aset.send_test_mail': 'Testmail sturen', 195 'asite.back_admin': 'Beheer', 196 'asite.title_new': 'Nieuwe site', 197 'asite.title_edit': 'Uiterlijk', 198 'asite.identity': 'Identiteit', 199 'asite.slug': 'Slug (URL)', 200 'asite.slug_fixed': '(vast)', 201 'asite.slug_placeholder': 'jouwslug', 202 'asite.field_title': 'Titel', 203 'asite.field_title_hint': '— getoond in de kop en als weergavenaam', 204 'asite.owner': 'Eigenaar', 205 'asite.owner_hint': '— wie deze Klonkt zelf mag beheren', 206 'asite.owner_god_suffix': ' (god)', 207 'asite.tagline': 'Tagline', 208 'asite.tagline_hint': '— korte oneliner', 209 'asite.bio': 'Bio / omschrijving', 210 'asite.bio_hint': '— getoond in de profielkop en gebruikt voor SEO', 211 'asite.profile_photo': 'Profielfoto', 212 'asite.photo_url_placeholder': '/media/avatars/foo.jpg of https://…', 213 'asite.photo_upload': '📷 Uploaden', 214 'asite.photo_clear': 'Verwijder', 215 'asite.language': 'Taal (ISO-code)', 216 'asite.profile_enabled': 'Profielkop tonen onder de navigatie', 217 'asite.appearance': 'Vormgeving', 218 'asite.accent_color': 'Accentkleur', 219 'asite.theme_default': 'Standaardthema voor nieuwe bezoekers', 220 'asite.theme_auto': 'Auto (volg device-voorkeur)', 221 'asite.theme_light': 'Licht', 222 'asite.theme_dark': 'Donker', 223 'asite.palette': 'Palet', 224 'asite.behavior': 'Gedrag', 225 'asite.is_public': 'Publieke site (uitvinken voor een besloten kring)', 226 'asite.robots_index': 'Zoekmachines mogen indexeren (sitemap.xml is verborgen indien uit)', 227 'asite.require_login_comment': 'Inloggen vereist om te reageren', 228 'asite.comment_moderation': 'Reactie-moderatie', 229 'asite.moderation_trust': 'Vertrouwen (automatisch goedkeuren)', 230 'asite.moderation_moderate': 'Modereren (eerst in de wachtrij)', 231 'asite.enable_audio': 'Audiospeler + embeds inschakelen', 232 'asite.enable_prutter': 'Prutter (DM’s tussen leden) inschakelen', 233 'asite.enable_prutter_hint': '— Hub-functie, premium', 234 'asite.links': 'Social / streaming-links', 235 'asite.links_hint': 'Getoond als merk-iconen op de profielkop. Voeg er zoveel toe als je wilt.', 236 'asite.link_add': '+ Link toevoegen', 237 'asite.feed_view': 'Feed-weergave', 238 'asite.feed_default': 'Standaardweergave voor de homepage', 239 'asite.feed_timeline': 'Tijdlijn (chronologische lijst)', 240 'asite.feed_grid': 'Grid (kaarten)', 241 'asite.feed_switch': 'Tijdlijn ↔ grid-schakelaar boven de feed tonen', 242 'asite.show_search': 'Zoekknop in de navigatie tonen', 243 'asite.show_archive': 'Archief-link in de navigatie tonen', 244 'asite.seo': 'SEO & social', 245 'asite.seo_pointer': 'Titel-sjabloon, canonical, deel-afbeelding, verificatie-metas en meer staan nu op een eigen pagina:', 246 'asite.seo_link': '🔎 SEO & vindbaarheid', 247 'asite.custom_legend': 'Eigen CSS & HTML', 248 'asite.optional': '(optioneel)', 249 'asite.custom_css': 'Eigen CSS (geïnjecteerd als <style> in <head>)', 250 'asite.custom_head': 'Eigen <head>-HTML (analytics, extra meta’s)', 251 'asite.custom_foot': 'Eigen footer-HTML', 252 'asite.submit_create': 'Site aanmaken', 253 'asite.submit_save': 'Wijzigingen opslaan', 254 'aseo.back': 'Beheer', 255 'aseo.title': 'SEO & vindbaarheid', 256 'aseo.tagline_pre': 'Geavanceerde SEO van', 257 'aseo.tagline_post': '— hoe je site in zoekmachines en bij het delen op social media verschijnt.', 258 'aseo.index_legend': 'Indexeren', 259 'aseo.index_label': 'Zoekmachines mogen deze site indexeren', 260 'aseo.index_hint_pre': 'uit =', 261 'aseo.index_hint_post': '+ sitemap.xml verborgen', 262 'aseo.title_legend': 'Titel & omschrijving', 263 'aseo.title_template': 'Titel-sjabloon', 264 'aseo.title_template_hint_pre': 'gebruik', 265 'aseo.title_template_hint_and': 'en', 266 'aseo.default_desc': 'Standaard-omschrijving', 267 'aseo.default_desc_hint': 'meta description / og:description als een pagina er geen heeft', 268 'aseo.default_desc_ph': 'Korte omschrijving van je site (max ~160 tekens werkt het best)', 269 'aseo.canonical': 'Canonieke basis-URL', 270 'aseo.canonical_hint': 'de productie-HTTPS-URL, voorkomt dubbele-content-straf', 271 'aseo.author': 'Auteur', 272 'aseo.author_hint': 'meta author-tag', 273 'aseo.author_ph': 'Jouw naam', 274 'aseo.social_legend': 'Delen op social media', 275 'aseo.og_image': 'Standaard deel-afbeelding (URL)', 276 'aseo.og_image_hint': 'og:image / Twitter-card; ~1200×630px', 277 'aseo.og_locale': 'Taal-locale', 278 'aseo.og_locale_hint_pre': 'og:locale, bv.', 279 'aseo.og_locale_hint_or': 'of', 280 'aseo.twitter': 'Twitter / X-handle', 281 'aseo.twitter_hint': 'met @', 282 'aseo.fb_app': 'Facebook App-ID', 283 'aseo.fb_app_hint': 'fb:app_id (optioneel)', 284 'aseo.publisher_legend': 'Uitgever (JSON-LD / rich results)', 285 'aseo.type': 'Type', 286 'aseo.type_person': 'Persoon', 287 'aseo.type_org': 'Organisatie / bedrijf', 288 'aseo.publisher_name': 'Naam', 289 'aseo.publisher_name_hint': 'valt terug op de site-titel', 290 'aseo.publisher_url': 'URL', 291 'aseo.publisher_logo': 'Logo (URL)', 292 'aseo.verify_legend': 'Zoekmachine-verificatie', 293 'aseo.verify_google': 'Google site-verificatie', 294 'aseo.verify_bing': 'Bing', 295 'aseo.verify_bing_hint': 'msvalidate.01', 296 'aseo.verify_pinterest': 'Pinterest', 297 'aseo.verify_pinterest_hint': 'p:domain_verify', 298 'aseo.verify_yandex': 'Yandex', 299 'aseo.save': 'SEO opslaan', 300 'agoog.title': 'Google-login', 301 'agoog.back': 'Instellingen', 302 'agoog.card_title': 'Google-login (bezoekers)', 303 'agoog.card_help_pre': 'Laat bezoekers met Google inloggen om te reageren — met je', 304 'agoog.card_help_own': 'eigen', 305 'agoog.card_help_mid': 'Google Cloud-client. Maak er een aan op', 306 'agoog.card_help_post': '(OAuth client-ID, type "Web application").', 307 'agoog.status_set': 'Status: ingesteld ✓', 308 'agoog.status_unset': 'Nog niet ingesteld.', 309 'agoog.guide_summary': 'Hoe stel ik dit in? (stap voor stap)', 310 'agoog.step1_pre': 'Ga naar de', 311 'agoog.step1_post': 'en maak (of kies) een project.', 312 'agoog.step2_usertype': 'User type', 313 'agoog.step2_scopes_pre': 'Voeg de scopes', 314 'agoog.step2_scopes_and': 'en', 315 'agoog.step2_scopes_post': 'toe.', 316 'agoog.step2_testmode': '(In testmodus: voeg jezelf toe als testgebruiker.)', 317 'agoog.step3_type': 'type', 318 'agoog.step4_pre': 'Bij', 319 'agoog.step4_post': ': plak de redirect-URI hieronder (moet exact matchen).', 320 'agoog.step5_pre': 'Kopieer de', 321 'agoog.step5_post': 'die je krijgt naar de velden hieronder en klik Opslaan.', 322 'agoog.note_label': 'Let op:', 323 'agoog.note_pre': 'je hoeft', 324 'agoog.note_noapi': 'geen API\'s aan te zetten', 325 'agoog.note_mid': '— "Google Cloud APIs", People API e.d. zijn', 326 'agoog.note_not': 'niet', 327 'agoog.note_post': 'nodig. Inloggen werkt via OpenID Connect (alleen het toestemmingsscherm + de OAuth-client).', 328 'agoog.redirect_label': 'Redirect-URI — kopieer deze en plak \'m in Google', 329 'agoog.rstep1_pre': 'Ga naar', 330 'agoog.rstep2': 'Open jouw OAuth client-ID (type "Web application").', 331 'agoog.rstep3_pre': 'Onder', 332 'agoog.rstep3_mid': 'klik', 333 'agoog.rstep3_post': 'en plak de regel hierboven.', 334 'agoog.rstep4_pre': 'Klik', 335 'agoog.rstep4_post': 'en wacht ~1 minuut.', 336 'agoog.warn_pre': 'Moet', 337 'agoog.warn_exact': 'exact', 338 'agoog.warn_mid1': 'kloppen:', 339 'agoog.warn_nohttp': 'geen', 340 'agoog.warn_mid2': ', juiste domein, en', 341 'agoog.warn_noslash': 'geen', 342 'agoog.warn_mid3': 'schuine streep aan het eind. Anders krijg je', 343 'agoog.warn_error': '"Error 400: redirect_uri_mismatch"', 344 'agoog.set_baseurl_pre': 'Stel eerst', 345 'agoog.set_baseurl_post': 'in — daar wordt de redirect-URI van afgeleid.', 346 'agoog.client_id': 'Client ID', 347 'agoog.client_secret': 'Client Secret', 348 'agoog.client_secret_set': 'ingesteld; leeg laten = ongewijzigd', 349 'agoog.secret_ph_set': '•••••••• (ingesteld)', 350 'agoog.save': 'Opslaan', 351 'agoog.disconnect': 'Loskoppelen', 352 'agoog.premium_locked': 'Premium-laag staat aan zonder Patreon — de Google-knop blijft verborgen tot premium ontgrendeld is.', 353 'aaud.title': 'Audio tracks', 354 'aaud.tagline_pre': 'MP3’s op site-niveau. Gebruik', 355 'aaud.tagline_post': 'in een post om een play-knop in te voegen.', 356 'aaud.upload': 'Upload', 357 'aaud.artist': 'Artiest', 358 'aaud.album': 'Album', 359 'aaud.applied_all': '(toegepast op alle bestanden)', 360 'aaud.optional': 'Optioneel', 361 'aaud.cover': 'Cover', 362 'aaud.cover_hint': '(optioneel, toegepast op alle bestanden — jpg/png/webp/gif, max 5 MB)', 363 'aaud.choose_cover': 'Kies cover', 364 'aaud.no_file': 'Geen bestand gekozen', 365 'aaud.drag_here': 'Sleep audio hierheen', 366 'aaud.or_click': 'of klik om bestanden te kiezen', 367 'aaud.start_upload': 'Start upload', 368 'aaud.clear_list': 'Wis lijst', 369 'aaud.tracks': 'Tracks', 370 'aaud.add_link_track': 'Track zonder audio', 371 'aaud.add_link_track_title': 'Een track zonder audiobestand — alleen titel + open-in links', 372 'aaud.no_tracks': 'Nog geen tracks. Upload er een hierboven.', 373 'aaud.untitled': '(zonder titel)', 374 'aaud.copy_click': 'Klik om te kopiëren', 375 'aaud.play': 'Afspelen', 376 'aaud.pause': 'Pauzeren', 377 'aaud.edit': 'Bewerken', 378 'aaud.delete': 'Verwijderen', 379 'aaud.delete_confirm': 'Track verwijderen?', 380 'aaud.dl_on': 'Download-voor-email staat AAN — klik om uit te zetten', 381 'aaud.dl_off': 'Download-voor-email staat uit — klik om aan te zetten', 382 'aaud.embed_player': 'Embedbare speler', 383 'aaud.embed_hint': 'Plak deze code op je eigen website/blog om je muziek met deze speler in te sluiten:', 384 'aaud.preview_player': 'Speler-voorbeeld openen', 385 'aaud.st_queued': 'Wachten', 386 'aaud.st_uploading': 'Uploaden…', 387 'aaud.st_transcoding': 'Converteren…', 388 'aaud.st_done': 'Klaar', 389 'aaud.st_error': 'Fout', 390 'aaud.err_unexpected': 'Onverwacht serverantwoord', 391 'aaud.failed': 'Mislukt', 392 'aaud.copied': 'gekopieerd', 393 'aaud.new_track': 'Nieuwe track', 394 'aaud.create_failed': 'Track aanmaken mislukt', 395 'aaud.editor_not_loaded': 'Track editor niet geladen', 396 'aaud.change_failed': 'Kon niet wijzigen', 397 'astat.title': 'Statistieken', 398 'astat.intro': 'Cookievrij gemeten — geen tracking-cookies, geen toestemmingsbanner. Bezoekers worden per dag geteld via een dagelijks roterende, anonieme hash (IP/browser worden niet bewaard). Je eigen beheerder-bezoeken en bekende bots/crawlers tellen niet mee.', 399 'astat.visitor_days': 'Bezoeker-dagen ({n}d)', 400 'astat.pageviews_days': 'Weergaven ({n}d)', 401 'astat.plays_total': 'Plays (totaal)', 402 'astat.postviews_total': 'Post-weergaven (totaal)', 403 'astat.alltime_pre': 'All-time:', 404 'astat.alltime_mid': 'weergaven', 405 'astat.alltime_post': 'bezoeker-dagen.', 406 'astat.help_summary': 'Wat betekenen deze cijfers?', 407 'astat.help_vd_term': 'Bezoeker-dagen', 408 'astat.help_vd_a': 'het aantal unieke bezoekers', 409 'astat.help_vd_em': 'per dag, bij elkaar opgeteld', 410 'astat.help_vd_b': '. Eén persoon die 5 dagen langskomt = 5 bezoeker-dagen. Cookieloos kan er niet over dagen heen geteld worden, dus dit is géén aantal unieke personen — het echte aantal mensen ligt (vaak fors) lager.', 411 'astat.help_pv_term': 'Weergaven', 412 'astat.help_pv': 'hoe vaak de home/feed of een post geladen is (ook bij klikken binnen de site). Andere pagina’s (agenda, downloads, links) tellen hier niet in mee.', 413 'astat.help_plays_term': 'Plays', 414 'astat.help_plays': 'totaal aantal keren dat een track is gestart.', 415 'astat.help_postviews_term': 'Post-weergaven', 416 'astat.help_postviews': 'totaal over alle posts samen.', 417 'astat.help_footer': 'Beheerder-bezoeken en bekende bots/crawlers worden overgeslagen. Het ruwe IP wordt nooit bewaard. Goed voor trends; neem absolute aantallen met een korrel zout.', 418 'astat.period': 'Periode:', 419 'astat.last_n_days': 'Laatste {n} dagen', 420 'astat.lg_visitor_days': 'Bezoeker-dagen', 421 'astat.lg_pageviews': 'Weergaven', 422 'astat.bar_title': '{day} — {pv} weergaven, {vd} bezoeker-dagen', 423 'astat.top_posts': 'Populairste posts', 424 'astat.no_views': 'Nog geen weergaven.', 425 'astat.most_played': 'Meest geluisterd', 426 'astat.no_plays': 'Nog geen plays.', 427 'astat.sources': 'Bronnen (waar bezoekers vandaan komen)', 428 'astat.linkbio_clicks': 'Link-in-bio klikken', 429 'apl.title': 'Playlists', 430 'apl.tagline_pre': 'Canonieke playlists. Bewerk een playlist hier en de wijzigingen werken door in álle posts die hem gebruiken via', 431 'apl.tagline_post': '.', 432 'apl.new_playlist': 'Nieuwe playlist', 433 'apl.none': 'Nog geen playlists.', 434 'apl.none_sub': 'Maak er een via de knop hierboven, of via de 📃 knop in de post-editor.', 435 'apl.pill_playlist': 'playlist', 436 'apl.pill_album': 'album', 437 'apl.track': 'track', 438 'apl.tracks': 'tracks', 439 'apl.copy_click': 'Klik om te kopiëren', 440 'apl.edit': 'Bewerken', 441 'apl.delete': 'Verwijderen', 442 'apl.copied': 'gekopieerd', 443 'apl.delete_confirm': 'Playlist "{title}" verwijderen? Posts die deze playlist embedden tonen vanaf nu een placeholder.', 444 'apl.delete_failed': 'Verwijderen mislukt', 445 'ausr.back': 'Beheer', 446 'ausr.title': 'Gebruikers', 447 'ausr.tagline_a': 'Beheer gebruikers, rollen, en verwijderingen. Rol', 448 'ausr.tagline_b': '= alles bekijken (incl. Beheer), niets wijzigen — handig voor demo\'s.', 449 'ausr.empty': 'Geen gebruikers.', 450 'ausr.you': 'jij', 451 'ausr.t_sites': 'Sites', 452 'ausr.t_posts': 'Posts', 453 'ausr.t_joined': 'Geregistreerd op', 454 'ausr.l_sites': 'sites', 455 'ausr.l_posts': 'posts', 456 'ausr.l_joined': 'lid sinds', 457 'ausr.new_klonkt': 'Nieuwe Klonkt voor deze gebruiker', 458 'ausr.new_klonkt_for': 'Nieuwe Klonkt voor {name}', 459 'ausr.role_label': 'Rol', 460 'ausr.role_kijker': 'kijker', 461 'ausr.role_member': 'member', 462 'ausr.role_admin': 'admin', 463 'ausr.role_god': 'god', 464 'ausr.delete': 'Verwijderen', 465 'ausr.del_warn': 'Dit verwijdert ook hun site + {n} post(s).', 466 'ausr.del_confirm': 'Gebruiker {name} verwijderen?', 467 'ausr.del_undo': 'Dit kan niet ongedaan worden.', 468 'asit2.back': 'Beheer', 469 'asit2.title': 'Sites', 470 'asit2.tagline': 'Beheer alle sites op deze installatie.', 471 'asit2.new_site': 'Nieuwe site', 472 'asit2.empty': 'Nog geen sites.', 473 'asit2.empty_sub': 'Maak er een via de knop hierboven.', 474 'asit2.pill_primary': 'primair', 475 'asit2.pill_primary_title': 'Hoofd-/labelsite van deze installatie', 476 'asit2.pill_public': 'public', 477 'asit2.pill_public_title': 'Publiek zichtbaar', 478 'asit2.pill_private': 'private', 479 'asit2.pill_private_title': 'Niet publiek', 480 'asit2.pill_noindex': 'noindex', 481 'asit2.pill_noindex_title': 'Niet geïndexeerd door zoekmachines', 482 'asit2.by': 'door', 483 'asit2.t_posts': 'Aantal posts', 484 'asit2.l_posts': 'posts', 485 'asit2.t_created': 'Aangemaakt op', 486 'asit2.l_created': 'aangemaakt', 487 'asit2.make_primary': 'Maak primair', 488 'asit2.make_primary_title': 'Maak primaire/hoofd-site', 489 'asit2.make_primary_confirm': 'Deze site instellen als de primaire/hoofd-site?', 490 'asit2.edit': 'Bewerken', 491 'asit2.delete': 'Verwijderen', 492 'asit2.delete_confirm': 'Site verwijderen? Lukt alleen als er geen posts zijn.', 493 'acom.back': 'Beheer', 494 'acom.title': 'Reactie-moderatie', 495 'acom.mode_for_site': 'Modus voor deze site:', 496 'acom.mode_trust_a': 'reacties worden automatisch goedgekeurd. Zet om naar', 497 'acom.mode_moderate_word': 'modereren', 498 'acom.site_settings': 'site-instellingen', 499 'acom.mode_trust_b': 'om ze in de wachtrij te zetten.', 500 'acom.mode_moderate_hint': 'nieuwe reacties moeten worden goedgekeurd voordat ze bij posts verschijnen.', 501 'acom.pending': 'In afwachting ({n})', 502 'acom.nothing_waiting': 'Niets in de wachtrij.', 503 'acom.reply': 'antwoord', 504 'acom.on': 'op', 505 'acom.approve': 'Goedkeuren', 506 'acom.reject': 'Afwijzen', 507 'acom.recent': 'Recente beslissingen', 508 'acom.nothing_yet': 'Nog niets.', 509 'acir.title': 'Cirkel', 510 'acir.back_settings': 'Instellingen', 511 'acir.circles': 'Cirkels', 512 'acir.settings': 'Instellingen', 513 'acir.mode_off_1': 'De modus staat niet op', 514 'acir.mode_off_2': '. Zet \'m aan bij', 515 'acir.mode_off_3': 'om je cirkel-feed te tonen op', 516 'acir.mode_off_4': '. Je kunt hieronder al wel bronnen klaarzetten.', 517 'acir.visibility_title': 'Mijn zichtbaarheid', 518 'acir.all_public': 'al-publieke', 519 'acir.visibility_help_1': 'Doe je mee aan cirkels? Dit maakt je', 520 'acir.visibility_help_2': 'posts ophaalbaar voor andere Klonkt-sites via een ondertekende feed', 521 'acir.visibility_help_3': 'Het is een deelname-keuze, geen privacy-slot: je posts blijven sowieso openbaar op je site, ook als dit uit staat. Wil je iets afschermen, maak die post dan niet-publiek.', 522 'acir.show_in_circles': 'Toon mijn site in cirkels van anderen', 523 'acir.save': 'Opslaan', 524 'acir.add_title': 'Klonkt-site toevoegen', 525 'acir.add_help': 'Plak de basis-URL van een andere Klonkt-site. Asymmetrisch: jij toont hen, los van of zij jou tonen.', 526 'acir.url': 'URL', 527 'acir.label': 'Label', 528 'acir.optional': 'optioneel', 529 'acir.label_ph': 'bijv. Joost Klein', 530 'acir.add': 'Toevoegen', 531 'acir.in_circle': 'In mijn cirkel ({n})', 532 'acir.no_sources': 'Nog geen bronnen. Voeg er hierboven een toe.', 533 'acir.sync_all': 'Alles nu synchroniseren', 534 'acir.st_active': 'actief', 535 'acir.posts': 'posts', 536 'acir.last': 'laatst', 537 'acir.st_mismatch': 'versie-mismatch', 538 'acir.mismatch_reason': 'Deze site draait een andere Klonkt-protocolversie — bijwerken nodig om te federeren.', 539 'acir.st_error': 'fout', 540 'acir.st_paused': 'gepauzeerd', 541 'acir.refresh': 'Verversen', 542 'acir.remove': 'Verwijder', 543 'acir.remove_confirm': 'Verwijderen uit je cirkel?', 544 'ashow.back_admin': 'Beheer', 545 'ashow.title': 'Agenda', 546 'ashow.show_toggle': 'Agenda tonen op de site', 547 'ashow.show_toggle_hint': '(Agenda-knop in de balk + de agendapagina)', 548 'ashow.save': 'Opslaan', 549 'ashow.off': 'uit', 550 'ashow.off_notice_1': 'De agenda staat nu', 551 'ashow.off_notice_2': '— bezoekers zien geen Agenda-knop en de agendapagina is niet bereikbaar. Zet \'m aan om je evenementen te tonen.', 552 'ashow.subscribers': 'abonnee(s) voor evenement-aankondigingen.', 553 'ashow.smtp_warn': '⚠ SMTP niet ingesteld — evenementen worden opgeslagen, maar notify-mails kunnen pas verstuurd worden zodra je SMTP invult.', 554 'ashow.f_date': 'Datum', 555 'ashow.f_time': 'Tijd (optioneel)', 556 'ashow.f_city': 'Plaats', 557 'ashow.f_country': 'Land (optioneel)', 558 'ashow.f_venue': 'Locatie/zaal (optioneel)', 559 'ashow.f_ticket': 'Ticket-URL (optioneel)', 560 'ashow.f_notes': 'Notitie (optioneel)', 561 'ashow.f_notes_ph': 'Support: ...', 562 'ashow.notify_label': 'Abonnees per e-mail op de hoogte brengen', 563 'ashow.smtp_required': '(SMTP vereist)', 564 'ashow.add_event': '+ Evenement toevoegen', 565 'ashow.del_confirm': 'Evenement verwijderen??', 566 'ashow.empty': 'Nog geen evenementen.', 567 'anews.title': 'Nieuwsbrief', 568 'anews.confirmed': 'bevestigd', 569 'anews.pending': 'in afwachting', 570 'anews.unsub': 'uitgeschreven', 571 'anews.smtp_warn_1': '⚠ SMTP is nog niet ingesteld. Aanmeldingen worden wél verzameld, maar versturen kan pas als je SMTP-gegevens invult', 572 'anews.smtp_warn_2': 'in', 573 'anews.share': 'Aanmeldlink om te delen:', 574 'anews.subject': 'Onderwerp', 575 'anews.subject_ph': 'Nieuwe single uit!', 576 'anews.body': 'Bericht', 577 'anews.body_ph': 'Schrijf je update…', 578 'anews.send_confirm': 'Nieuwsbrief versturen naar {n} bevestigde abonnee(s)?', 579 'anews.send_btn': 'Versturen naar {n} abonnee(s)', 580 'anews.sent_heading': 'Verstuurd', 581 'anews.recipients': 'ontvanger(s)', 582 'aupd.title': 'Updates', 583 'aupd.back_admin': 'Beheer', 584 'aupd.version_heading': 'Versie van deze Klonkt', 585 'aupd.app_version': 'App-versie', 586 'aupd.current': 'Huidig', 587 'aupd.current_unknown': 'onbekend (nog niet via de update-knop bijgewerkt)', 588 'aupd.latest': 'Nieuwste', 589 'aupd.latest_failed': 'kon de nieuwste versie niet ophalen', 590 'aupd.no_source': 'Geen update-bron bereikbaar', 591 'aupd.uptodate': 'Up-to-date', 592 'aupd.update_available': 'Update beschikbaar', 593 'aupd.behind_one': '{n} commit achter', 594 'aupd.behind_many': '{n} commits achter', 595 'aupd.run_confirm': 'De site wordt naar de nieuwste versie gebracht en herstart kort. Doorgaan?', 596 'aupd.redeploy': 'Opnieuw uitrollen', 597 'aupd.update_now': 'Nu bijwerken', 598 'aupd.help': 'Bijwerken haalt de nieuwste code op en herstart deze site kort (~10s). Doe dit rustig — er gaat niets verloren (je posts, instellingen en cirkel blijven staan).', 599 'aepk.title': 'Perskit bewerken', 600 'aepk.back_admin': 'Beheer', 601 'aepk.view_epk': 'Bekijk perskit', 602 'aepk.text_heading': 'Tekst', 603 'aepk.text_help': 'De perskit (/pers) toont je sitenaam + foto, deze bio en contact, plus automatisch je meest beluisterde nummers en recente posts. Laat de bio leeg om de site-tagline te gebruiken; laat contact leeg om niets te tonen (je login-mail wordt nooit automatisch getoond).', 604 'aepk.bio_label': 'Pers-bio', 605 'aepk.bio_ph': 'Korte beschrijving van jou/het project voor pers & boekers.', 606 'aepk.contact_label': 'Pers-contact', 607 'aepk.contact_ph': 'bv. pers@jouwdomein.nl of een boekingslink', 608 'aepk.tracks_label': 'Nummers op de perskit', 609 'aepk.tracks_hint': '(kies er max {n}; laat leeg voor automatisch de top {n} meest beluisterd)', 610 'aepk.no_tracks': 'Nog geen nummers — voeg eerst audio toe in Beheer → Audio.', 611 'aepk.untitled': '(zonder titel)', 612 'aepk.save': 'Opslaan', 613 'ahelp.back': 'Beheer', 614 'ahelp.title': 'Handleiding', 615 'ahelp.intro': 'Uitleg van alle functies. Typ hieronder om te zoeken op een onderwerp of instructie.', 616 'ahelp.search_placeholder': 'Zoek… (bv. \'agenda\', \'foto\', \'cirkel\', \'downloads\')', 617 'ahelp.search_aria': 'Zoek in de handleiding', 618 'ahelp.premium': 'premium', 619 'ahelp.empty': 'Geen onderwerpen gevonden voor je zoekopdracht.', 620 'ahelp.s_newpost_h': 'Nieuwe post schrijven', 621 'ahelp.s_newpost_b': 'Beheer → <strong>Nieuwe post</strong>. Geef een titel en schrijf je inhoud. Onderaan kies je de status: <em>concept</em> (niet zichtbaar) of <em>gepubliceerd</em>. Concepten staan bovenaan in je Beheer-overzicht zodat je ze terugvindt.', 622 'ahelp.s_excerpt_h': 'Samenvatting & Cirkel Preview', 623 'ahelp.s_excerpt_b': 'Het veld <strong>Samenvatting & Cirkel Preview</strong> (de excerpt) is de korte previewtekst onder een post in lijsten, én de samenvatting die andere sites tonen als ze je post via een <strong>Cirkel</strong> overnemen. Laat je \'m leeg, dan wordt automatisch het begin van de post gebruikt.', 624 'ahelp.s_pin_h': 'Post pinnen / volgorde', 625 'ahelp.s_pin_b': 'In de post-editor kun je een post <strong>pinnen</strong> met een rang (1 = bovenaan). Gepinde posts staan vooraan in de tijdlijn/grid, op volgorde van hun rang. Rang leeg of 0 = niet gepind.', 626 'ahelp.s_schedule_h': 'Publiceren plannen & alleen voor fans', 627 'ahelp.s_schedule_b': 'In de editor kun je een <strong>publicatiedatum</strong> in de toekomst zetten — de post verschijnt dan automatisch op dat moment. Met <strong>Alleen voor fans</strong> zien niet-ingelogde bezoekers alleen een teaser + login-uitnodiging; ingelogde fans zien alles.', 628 'ahelp.s_images_h': 'Afbeeldingen in posts', 629 'ahelp.s_images_b': 'Afbeeldingen in de tekst en de cover-afbeelding worden altijd <strong>volledig</strong> getoond op de volledige breedte (niet bijgesneden), met hun natuurlijke hoogte.', 630 'ahelp.s_audio_h': 'Audio & nummers toevoegen', 631 'ahelp.s_audio_b': 'Beheer → <strong>Audio</strong>. Upload een bestand of voeg een <em>link-only</em> nummer toe (zonder upload, alleen "open in"-links). Per nummer vul je titel, artiest, cover, en optioneel album/positie in. In een post toon je een nummer met de shortcode <code>[[track:id]]</code>, een album met <code>[[album:Naam]]</code>, een playlist met <code>[[playlist:id]]</code>.', 632 'ahelp.s_credit_h': 'Credit, licentie & "open in"', 633 'ahelp.s_credit_b': 'Per nummer kun je een <strong>eigenaar/credit</strong> (met © -knop) en een <strong>licentie</strong> instellen — die worden ook in de mp3-metadata geschreven. Met de <strong>open-in</strong>-velden (Spotify / YouTube / SoundCloud) verschijnen knoppen om het nummer op die platforms te openen.', 634 'ahelp.s_downloads_h': 'Downloads', 635 'ahelp.s_downloads_b': 'Markeer een nummer als <strong>downloadbaar</strong> in Beheer → Audio (⬇-knop). Bezoekers vinden ze op de <strong>/downloads</strong>-pagina en laten hun e-mail achter om het bestand te krijgen (komt op je mailinglijst). Wil je downloads prominent in de feed? Maak een gewone post met slug <code>downloads</code> en pin \'m.', 636 'ahelp.s_albums_h': 'Albums & playlists', 637 'ahelp.s_albums_b': 'Geef nummers hetzelfde <strong>album</strong> + een <strong>positie</strong> om een album te vormen. Playlists maak je in Beheer → <strong>Playlists</strong>. Beide toon je in een post met <code>[[album:Naam]]</code> of <code>[[playlist:id]]</code>.', 638 'ahelp.s_agenda_h': 'Agenda / evenementen', 639 'ahelp.s_agenda_b': 'Beheer → <strong>Agenda</strong>. Zet bovenaan <strong>"Agenda tonen op de site"</strong> aan — dan verschijnt de Agenda-knop in de balk en is de agendapagina bereikbaar. Voeg evenementen toe (datum, plaats, locatie, tickets). Bezoekers kunnen zich (los van de nieuwsbrief) aanmelden voor een seintje bij een nieuw evenement.', 640 'ahelp.s_presskit_h': 'Perskit', 641 'ahelp.s_presskit_b': 'Een deelbare perspagina op <strong>/pers</strong>. Bewerk \'m via de <strong>✎ Bewerken</strong>-knop op die pagina zelf (alleen jij ziet die). Stel een korte pers-bio + contact in, en kies <strong>tot 5 nummers</strong> die getoond worden (of laat leeg = automatisch de top-5 meest beluisterd).', 642 'ahelp.s_circles_h': 'Cirkels (federatie)', 643 'ahelp.s_circles_b': 'In Beheer → Instellingen zet je je site op <strong>Cirkel</strong>-modus. Daarna voeg je in Beheer → <strong>Cirkel</strong> andere Klonkt-sites toe; hun publieke posts verschijnen dan in je <strong>/cirkel</strong>-feed. Wat anderen van jóu zien is de titel + de "Samenvatting & Cirkel Preview". Hubs doen niet mee aan cirkels (alleen solo-sites).', 644 'ahelp.s_stats_h': 'Statistieken', 645 'ahelp.s_stats_b': 'Beheer → <strong>Statistieken</strong>. Cookievrij gemeten. <strong>Bezoeker-dagen</strong> = unieke bezoekers per dag, opgeteld (géén aantal personen). <strong>Weergaven</strong> = home/feed- en post-loads. Beheerder-bezoeken en bots tellen niet mee. Goed voor trends; absolute aantallen met een korrel zout.', 646 'ahelp.s_newsletter_h': 'Nieuwsbrief', 647 'ahelp.s_newsletter_b': 'Beheer → <strong>Nieuwsbrief</strong>: stel een bericht op en stuur het naar je bevestigde abonnees. Bezoekers melden zich aan via de footer of <strong>/nieuwsbrief</strong>. Versturen vereist dat e-mail (SMTP) is ingesteld.', 648 'ahelp.s_linkbio_h': 'Link-in-bio', 649 'ahelp.s_linkbio_b': 'Een Linktree-achtige pagina op <strong>/links</strong> met je profiel-links. De kliks per link zie je terug in Statistieken.', 650 'ahelp.s_embed_h': 'Embedbare speler', 651 'ahelp.s_embed_b': 'Beheer → Audio toont een kopieerbare <code><iframe></code>-code (<strong>/embed</strong>) waarmee je je speler op een andere website kunt insluiten.', 652 'ahelp.s_appearance_h': 'Uiterlijk (thema, foto, accent)', 653 'ahelp.s_appearance_b': 'Beheer → <strong>Uiterlijk</strong>: stel je sitenaam, tagline, profielfoto, accentkleur en kleurpalet in, en de standaard feed-weergave (Tijdlijn of Grid).', 654 'ahelp.s_tenancy_h': 'Solo- / Hub-modus', 655 'ahelp.s_tenancy_b': 'Beheer → <strong>Instellingen</strong>: <em>Solo</em> = één site (jij). <em>Hub</em> = een bedrijfs-/labelsite met meerdere makers, elk een eigen Klonkt onder <code>/user/<naam></code>. <em>Cirkel</em> = solo + federatie met andere sites.', 656 'ahelp.s_premium_h': 'Premium / Patreon', 657 'ahelp.s_premium_b': 'Premium-functies (Hub, Statistieken, Agenda, Downloads, Perskit, Nieuwsbrief, Link-in-bio, Embed, fan-login) ontgrendel je in Beheer → Instellingen door Patreon te koppelen ($10 lifetime). Updates en de kernapp blijven altijd gratis.', 658 'ahelp.s_google_h': 'Google-login (bezoekers)', 659 'ahelp.s_google_b': 'Beheer → Instellingen → <strong>Google</strong>. Optioneel: laat bezoekers/fans met Google inloggen om te reageren. Jij beheert met je wachtwoord — Google geeft nooit beheerrechten. Vul je eigen Google-client-gegevens in (per site).', 660 'ahelp.s_updates_h': 'Updates', 661 'ahelp.s_updates_b': 'Beheer → <strong>Updates</strong> (alleen god) toont de huidige versie en of er een nieuwere is. Met "Nu bijwerken" haal je de laatste versie binnen.', 112 662 }, 113 663 en: { … … 203 753 'search.empty': 'Nothing found.', 'search.in_post': 'in post →', 204 754 'like.login_title': 'Log in to like this post', 755 // === Admin sub-pages === 756 'aset.title': 'Settings', 757 'aset.back_admin': 'Admin', 758 'aset.mode': 'Mode', 759 'aset.mode_help': 'Determines how this installation works. Switching is safe: nothing is deleted — Solo just hides the multi-site features and shows your primary site.', 760 'aset.solo': 'Solo', 761 'aset.solo_title': 'one site (yours).', 762 'aset.solo_desc': 'No user directory, no site switching. Visitors can still sign in with Google to comment.', 763 'aset.hub': 'Hub', 764 'aset.hub_title': 'company site with', 765 'aset.premium_badge': 'premium', 766 'aset.hub_desc': 'You (the admin) assign users their own Klonkt Hub. Closed circle: no open registration.', 767 'aset.hub_locked_note': 'Requires premium — connect Patreon on this page.', 768 'aset.circle': 'Circles', 769 'aset.circle_title': 'solo + federation.', 770 'aset.circle_desc': 'A single site of your own that shows the public posts of other Klonkt sites. Asymmetric: you decide who is in your circle.', 771 'aset.save': 'Save', 772 'aset.hub_home': 'Hub home page', 773 'aset.hub_home_help_1': 'The public hub page', 774 'aset.hub_home_help_2': 'is generic — it belongs to no single user. Here you set the name and intro. Your own Klonkt Hub stays separate under', 775 'aset.name': 'Name', 776 'aset.name_ph': 'e.g. Studio Noord', 777 'aset.tagline': 'Tagline', 778 'aset.tagline_ph': 'e.g. Independent music label', 779 'aset.intro': 'Intro', 780 'aset.intro_ph': 'Short intro text below the title.', 781 'aset.hero_image': 'Hero image (URL)', 782 'aset.hero_image_hint': 'optional; background of the hero', 783 'aset.hero_upload': '…or upload an image', 784 'aset.hero_upload_hint': 'jpg/png/webp/gif, max 5 MB; replaces the URL above', 785 'aset.hero_overlay': 'Dark overlay', 786 'aset.hero_overlay_hint': 'darkens the hero so the text stays readable', 787 'aset.preview_overlay': 'Preview (with overlay):', 788 'aset.hero_preview_alt': 'Hero preview', 789 'aset.your_circle': 'Your circle', 790 'aset.your_circle_help': 'Manage which other Klonkt sites you show in your circle, and whether your site may appear in other people\'s circles. Asymmetric: you decide who you follow.', 791 'aset.manage_circle': 'Manage your circle', 792 'aset.premium': 'Premium (Patreon)', 793 'aset.premium_help_1': 'Unlock the premium modules (Hub mode, Statistics, Fan login, Prutter DMs) with your', 794 'aset.premium_lifetime': '$10 lifetime', 795 'aset.premium_help_2': 'Patreon support. The app and all updates stay free.', 796 'aset.premium_active': 'Premium active.', 797 'aset.lifetime_support': 'Lifetime support:', 798 'aset.patreon_disconnect': 'Disconnect Patreon', 799 'aset.patreon_no_lifetime': 'Patreon connected, but no $10 lifetime yet', 800 'aset.now': 'now', 801 'aset.patreon_support_again': 'Support the campaign and reconnect.', 802 'aset.patreon_reconnect': 'Reconnect', 803 'aset.patreon_not_connected': 'Not connected yet.', 804 'aset.patreon_connect': 'Connect Patreon', 805 'aset.google_login': 'Google login (visitors)', 806 'aset.google_login_help': 'Let visitors sign in with Google to comment — using your own Google Cloud client.', 807 'aset.status_set': 'Status: configured', 808 'aset.not_set_yet': 'Not configured yet.', 809 'aset.google_login_setup': 'Set up Google login', 810 'aset.newsletter': 'Newsletter', 811 'aset.newsletter_help_1': 'Show a', 812 'aset.newsletter_footer_field': 'sign-up field in the footer', 813 'aset.newsletter_help_2': 'of your site so visitors can subscribe from any page. (The full sign-up page stays at', 814 'aset.newsletter_show_footer': 'Show sign-up field in the footer', 815 'aset.email_smtp': 'Email (SMTP)', 816 'aset.smtp_help_1': 'Needed to', 817 'aset.smtp_help_newsletter': 'send the newsletter', 818 'aset.smtp_help_2': ',', 819 'aset.smtp_help_notify': 'show-notify', 820 'aset.smtp_help_3': 'emails and to make password reset by email work. Enter your mail provider\'s details (e.g. your hosting mail, a Gmail app password, Brevo, Mailgun…).', 821 'aset.via_env': 'via .env', 822 'aset.smtp_not_set': 'Not configured yet — sending does not work yet.', 823 'aset.smtp_host': 'SMTP host', 824 'aset.smtp_port': 'Port', 825 'aset.smtp_port_hint': '587 (STARTTLS) or 465 (SSL)', 826 'aset.smtp_user': 'Username', 827 'aset.smtp_pass': 'Password', 828 'aset.smtp_pass_set_hint': 'set; leave empty = unchanged', 829 'aset.smtp_pass_ph_set': '•••••••• (set)', 830 'aset.smtp_pass_ph': 'app password', 831 'aset.smtp_from': 'Sender', 832 'aset.smtp_from_hint': 'optional; defaults to username', 833 'aset.smtp_from_ph': 'Your Name <you@yourprovider.com>', 834 'aset.smtp_save': 'Save SMTP', 835 'aset.clear': 'Clear', 836 'aset.test_mail_to': 'Send test mail to', 837 'aset.send_test_mail': 'Send test mail', 838 'asite.back_admin': 'Admin', 839 'asite.title_new': 'New site', 840 'asite.title_edit': 'Appearance', 841 'asite.identity': 'Identity', 842 'asite.slug': 'Slug (URL)', 843 'asite.slug_fixed': '(fixed)', 844 'asite.slug_placeholder': 'yourslug', 845 'asite.field_title': 'Title', 846 'asite.field_title_hint': '— shown in the header and as the display name', 847 'asite.owner': 'Owner', 848 'asite.owner_hint': '— who may manage this Klonkt themselves', 849 'asite.owner_god_suffix': ' (god)', 850 'asite.tagline': 'Tagline', 851 'asite.tagline_hint': '— short one-liner', 852 'asite.bio': 'Bio / description', 853 'asite.bio_hint': '— shown in the profile header and used for SEO', 854 'asite.profile_photo': 'Profile photo', 855 'asite.photo_url_placeholder': '/media/avatars/foo.jpg or https://…', 856 'asite.photo_upload': '📷 Upload', 857 'asite.photo_clear': 'Remove', 858 'asite.language': 'Language (ISO code)', 859 'asite.profile_enabled': 'Show profile header below the navigation', 860 'asite.appearance': 'Appearance', 861 'asite.accent_color': 'Accent color', 862 'asite.theme_default': 'Default theme for new visitors', 863 'asite.theme_auto': 'Auto (follow device preference)', 864 'asite.theme_light': 'Light', 865 'asite.theme_dark': 'Dark', 866 'asite.palette': 'Palette', 867 'asite.behavior': 'Behavior', 868 'asite.is_public': 'Public site (uncheck for a private circle)', 869 'asite.robots_index': 'Allow search engines to index (sitemap.xml is hidden when off)', 870 'asite.require_login_comment': 'Login required to comment', 871 'asite.comment_moderation': 'Comment moderation', 872 'asite.moderation_trust': 'Trust (approve automatically)', 873 'asite.moderation_moderate': 'Moderate (queue first)', 874 'asite.enable_audio': 'Enable audio player + embeds', 875 'asite.enable_prutter': 'Enable Prutter (DMs between members)', 876 'asite.enable_prutter_hint': '— Hub feature, premium', 877 'asite.links': 'Social / streaming links', 878 'asite.links_hint': 'Shown as brand icons in the profile header. Add as many as you like.', 879 'asite.link_add': '+ Add link', 880 'asite.feed_view': 'Feed display', 881 'asite.feed_default': 'Default view for the homepage', 882 'asite.feed_timeline': 'Timeline (chronological list)', 883 'asite.feed_grid': 'Grid (cards)', 884 'asite.feed_switch': 'Show timeline ↔ grid switch above the feed', 885 'asite.show_search': 'Show search button in the navigation', 886 'asite.show_archive': 'Show archive link in the navigation', 887 'asite.seo': 'SEO & social', 888 'asite.seo_pointer': 'Title template, canonical, share image, verification metas and more now live on their own page:', 889 'asite.seo_link': '🔎 SEO & discoverability', 890 'asite.custom_legend': 'Custom CSS & HTML', 891 'asite.optional': '(optional)', 892 'asite.custom_css': 'Custom CSS (injected as <style> in <head>)', 893 'asite.custom_head': 'Custom <head> HTML (analytics, extra metas)', 894 'asite.custom_foot': 'Custom footer HTML', 895 'asite.submit_create': 'Create site', 896 'asite.submit_save': 'Save changes', 897 'aseo.back': 'Admin', 898 'aseo.title': 'SEO & discoverability', 899 'aseo.tagline_pre': 'Advanced SEO for', 900 'aseo.tagline_post': '— how your site appears in search engines and when shared on social media.', 901 'aseo.index_legend': 'Indexing', 902 'aseo.index_label': 'Allow search engines to index this site', 903 'aseo.index_hint_pre': 'off =', 904 'aseo.index_hint_post': '+ sitemap.xml hidden', 905 'aseo.title_legend': 'Title & description', 906 'aseo.title_template': 'Title template', 907 'aseo.title_template_hint_pre': 'use', 908 'aseo.title_template_hint_and': 'and', 909 'aseo.default_desc': 'Default description', 910 'aseo.default_desc_hint': 'meta description / og:description when a page has none', 911 'aseo.default_desc_ph': 'Short description of your site (max ~160 characters works best)', 912 'aseo.canonical': 'Canonical base URL', 913 'aseo.canonical_hint': 'the production HTTPS URL, prevents duplicate-content penalties', 914 'aseo.author': 'Author', 915 'aseo.author_hint': 'meta author tag', 916 'aseo.author_ph': 'Your name', 917 'aseo.social_legend': 'Sharing on social media', 918 'aseo.og_image': 'Default share image (URL)', 919 'aseo.og_image_hint': 'og:image / Twitter card; ~1200×630px', 920 'aseo.og_locale': 'Language locale', 921 'aseo.og_locale_hint_pre': 'og:locale, e.g.', 922 'aseo.og_locale_hint_or': 'or', 923 'aseo.twitter': 'Twitter / X handle', 924 'aseo.twitter_hint': 'with @', 925 'aseo.fb_app': 'Facebook App ID', 926 'aseo.fb_app_hint': 'fb:app_id (optional)', 927 'aseo.publisher_legend': 'Publisher (JSON-LD / rich results)', 928 'aseo.type': 'Type', 929 'aseo.type_person': 'Person', 930 'aseo.type_org': 'Organization / company', 931 'aseo.publisher_name': 'Name', 932 'aseo.publisher_name_hint': 'falls back to the site title', 933 'aseo.publisher_url': 'URL', 934 'aseo.publisher_logo': 'Logo (URL)', 935 'aseo.verify_legend': 'Search engine verification', 936 'aseo.verify_google': 'Google site verification', 937 'aseo.verify_bing': 'Bing', 938 'aseo.verify_bing_hint': 'msvalidate.01', 939 'aseo.verify_pinterest': 'Pinterest', 940 'aseo.verify_pinterest_hint': 'p:domain_verify', 941 'aseo.verify_yandex': 'Yandex', 942 'aseo.save': 'Save SEO', 943 'agoog.title': 'Google login', 944 'agoog.back': 'Settings', 945 'agoog.card_title': 'Google login (visitors)', 946 'agoog.card_help_pre': 'Let visitors log in with Google to comment — using your', 947 'agoog.card_help_own': 'own', 948 'agoog.card_help_mid': 'Google Cloud client. Create one at', 949 'agoog.card_help_post': '(OAuth client ID, type "Web application").', 950 'agoog.status_set': 'Status: configured ✓', 951 'agoog.status_unset': 'Not configured yet.', 952 'agoog.guide_summary': 'How do I set this up? (step by step)', 953 'agoog.step1_pre': 'Go to the', 954 'agoog.step1_post': 'and create (or pick) a project.', 955 'agoog.step2_usertype': 'User type', 956 'agoog.step2_scopes_pre': 'Add the scopes', 957 'agoog.step2_scopes_and': 'and', 958 'agoog.step2_scopes_post': '.', 959 'agoog.step2_testmode': '(In testing mode: add yourself as a test user.)', 960 'agoog.step3_type': 'type', 961 'agoog.step4_pre': 'Under', 962 'agoog.step4_post': ': paste the redirect URI below (it must match exactly).', 963 'agoog.step5_pre': 'Copy the', 964 'agoog.step5_post': 'you receive into the fields below and click Save.', 965 'agoog.note_label': 'Note:', 966 'agoog.note_pre': 'you do', 967 'agoog.note_noapi': 'not need to enable any APIs', 968 'agoog.note_mid': '— "Google Cloud APIs", People API and the like are', 969 'agoog.note_not': 'not', 970 'agoog.note_post': 'required. Login works via OpenID Connect (just the consent screen + the OAuth client).', 971 'agoog.redirect_label': 'Redirect URI — copy this and paste it into Google', 972 'agoog.rstep1_pre': 'Go to', 973 'agoog.rstep2': 'Open your OAuth client ID (type "Web application").', 974 'agoog.rstep3_pre': 'Under', 975 'agoog.rstep3_mid': 'click', 976 'agoog.rstep3_post': 'and paste the line above.', 977 'agoog.rstep4_pre': 'Click', 978 'agoog.rstep4_post': 'and wait ~1 minute.', 979 'agoog.warn_pre': 'It must be', 980 'agoog.warn_exact': 'exactly', 981 'agoog.warn_mid1': 'right:', 982 'agoog.warn_nohttp': 'not', 983 'agoog.warn_mid2': ', the correct domain, and', 984 'agoog.warn_noslash': 'no', 985 'agoog.warn_mid3': 'trailing slash. Otherwise you get', 986 'agoog.warn_error': '"Error 400: redirect_uri_mismatch"', 987 'agoog.set_baseurl_pre': 'First set', 988 'agoog.set_baseurl_post': '— the redirect URI is derived from it.', 989 'agoog.client_id': 'Client ID', 990 'agoog.client_secret': 'Client Secret', 991 'agoog.client_secret_set': 'configured; leave blank to keep unchanged', 992 'agoog.secret_ph_set': '•••••••• (configured)', 993 'agoog.save': 'Save', 994 'agoog.disconnect': 'Disconnect', 995 'agoog.premium_locked': 'The premium tier is on without Patreon — the Google button stays hidden until premium is unlocked.', 996 'aaud.title': 'Audio tracks', 997 'aaud.tagline_pre': 'Site-level MP3s. Use', 998 'aaud.tagline_post': 'in a post to insert a play button.', 999 'aaud.upload': 'Upload', 1000 'aaud.artist': 'Artist', 1001 'aaud.album': 'Album', 1002 'aaud.applied_all': '(applied to all files)', 1003 'aaud.optional': 'Optional', 1004 'aaud.cover': 'Cover', 1005 'aaud.cover_hint': '(optional, applied to all files — jpg/png/webp/gif, max 5 MB)', 1006 'aaud.choose_cover': 'Choose cover', 1007 'aaud.no_file': 'No file chosen', 1008 'aaud.drag_here': 'Drag audio here', 1009 'aaud.or_click': 'or click to choose files', 1010 'aaud.start_upload': 'Start upload', 1011 'aaud.clear_list': 'Clear list', 1012 'aaud.tracks': 'Tracks', 1013 'aaud.add_link_track': 'Track without audio', 1014 'aaud.add_link_track_title': 'A track without an audio file — title + open-in links only', 1015 'aaud.no_tracks': 'No tracks yet. Upload one above.', 1016 'aaud.untitled': '(untitled)', 1017 'aaud.copy_click': 'Click to copy', 1018 'aaud.play': 'Play', 1019 'aaud.pause': 'Pause', 1020 'aaud.edit': 'Edit', 1021 'aaud.delete': 'Delete', 1022 'aaud.delete_confirm': 'Delete track?', 1023 'aaud.dl_on': 'Download-for-email is ON — click to turn off', 1024 'aaud.dl_off': 'Download-for-email is off — click to turn on', 1025 'aaud.embed_player': 'Embeddable player', 1026 'aaud.embed_hint': 'Paste this code on your own website/blog to embed your music with this player:', 1027 'aaud.preview_player': 'Open player preview', 1028 'aaud.st_queued': 'Waiting', 1029 'aaud.st_uploading': 'Uploading…', 1030 'aaud.st_transcoding': 'Converting…', 1031 'aaud.st_done': 'Done', 1032 'aaud.st_error': 'Error', 1033 'aaud.err_unexpected': 'Unexpected server response', 1034 'aaud.failed': 'Failed', 1035 'aaud.copied': 'copied', 1036 'aaud.new_track': 'New track', 1037 'aaud.create_failed': 'Could not create track', 1038 'aaud.editor_not_loaded': 'Track editor not loaded', 1039 'aaud.change_failed': 'Could not change', 1040 'astat.title': 'Statistics', 1041 'astat.intro': 'Measured cookie-free — no tracking cookies, no consent banner. Visitors are counted per day via a daily-rotating, anonymous hash (IP/browser are not stored). Your own admin visits and known bots/crawlers are not counted.', 1042 'astat.visitor_days': 'Visitor-days ({n}d)', 1043 'astat.pageviews_days': 'Views ({n}d)', 1044 'astat.plays_total': 'Plays (total)', 1045 'astat.postviews_total': 'Post views (total)', 1046 'astat.alltime_pre': 'All-time:', 1047 'astat.alltime_mid': 'views', 1048 'astat.alltime_post': 'visitor-days.', 1049 'astat.help_summary': 'What do these numbers mean?', 1050 'astat.help_vd_term': 'Visitor-days', 1051 'astat.help_vd_a': 'the number of unique visitors', 1052 'astat.help_vd_em': 'per day, added together', 1053 'astat.help_vd_b': '. One person who visits on 5 days = 5 visitor-days. Without cookies it is impossible to count across days, so this is not a count of unique people — the real number of people is (often much) lower.', 1054 'astat.help_pv_term': 'Views', 1055 'astat.help_pv': 'how often the home/feed or a post has been loaded (including clicks within the site). Other pages (agenda, downloads, links) are not counted here.', 1056 'astat.help_plays_term': 'Plays', 1057 'astat.help_plays': 'total number of times a track has been started.', 1058 'astat.help_postviews_term': 'Post views', 1059 'astat.help_postviews': 'total across all posts combined.', 1060 'astat.help_footer': 'Admin visits and known bots/crawlers are skipped. The raw IP is never stored. Good for trends; take absolute numbers with a grain of salt.', 1061 'astat.period': 'Period:', 1062 'astat.last_n_days': 'Last {n} days', 1063 'astat.lg_visitor_days': 'Visitor-days', 1064 'astat.lg_pageviews': 'Views', 1065 'astat.bar_title': '{day} — {pv} views, {vd} visitor-days', 1066 'astat.top_posts': 'Most popular posts', 1067 'astat.no_views': 'No views yet.', 1068 'astat.most_played': 'Most listened', 1069 'astat.no_plays': 'No plays yet.', 1070 'astat.sources': 'Sources (where visitors come from)', 1071 'astat.linkbio_clicks': 'Link-in-bio clicks', 1072 'apl.title': 'Playlists', 1073 'apl.tagline_pre': 'Canonical playlists. Edit a playlist here and the changes carry through to every post that uses it via', 1074 'apl.tagline_post': '.', 1075 'apl.new_playlist': 'New playlist', 1076 'apl.none': 'No playlists yet.', 1077 'apl.none_sub': 'Create one with the button above, or via the 📃 button in the post editor.', 1078 'apl.pill_playlist': 'playlist', 1079 'apl.pill_album': 'album', 1080 'apl.track': 'track', 1081 'apl.tracks': 'tracks', 1082 'apl.copy_click': 'Click to copy', 1083 'apl.edit': 'Edit', 1084 'apl.delete': 'Delete', 1085 'apl.copied': 'copied', 1086 'apl.delete_confirm': 'Delete playlist "{title}"? Posts that embed this playlist will now show a placeholder.', 1087 'apl.delete_failed': 'Delete failed', 1088 'ausr.back': 'Admin', 1089 'ausr.title': 'Users', 1090 'ausr.tagline_a': 'Manage users, roles, and deletions. The', 1091 'ausr.tagline_b': 'role = view everything (including Admin), change nothing — handy for demos.', 1092 'ausr.empty': 'No users.', 1093 'ausr.you': 'you', 1094 'ausr.t_sites': 'Sites', 1095 'ausr.t_posts': 'Posts', 1096 'ausr.t_joined': 'Registered on', 1097 'ausr.l_sites': 'sites', 1098 'ausr.l_posts': 'posts', 1099 'ausr.l_joined': 'joined', 1100 'ausr.new_klonkt': 'New Klonkt for this user', 1101 'ausr.new_klonkt_for': 'New Klonkt for {name}', 1102 'ausr.role_label': 'Role', 1103 'ausr.role_kijker': 'viewer', 1104 'ausr.role_member': 'member', 1105 'ausr.role_admin': 'admin', 1106 'ausr.role_god': 'god', 1107 'ausr.delete': 'Delete', 1108 'ausr.del_warn': 'This also deletes their site + {n} post(s).', 1109 'ausr.del_confirm': 'Delete user {name}?', 1110 'ausr.del_undo': 'This cannot be undone.', 1111 'asit2.back': 'Admin', 1112 'asit2.title': 'Sites', 1113 'asit2.tagline': 'Manage all sites on this installation.', 1114 'asit2.new_site': 'New site', 1115 'asit2.empty': 'No sites yet.', 1116 'asit2.empty_sub': 'Create one using the button above.', 1117 'asit2.pill_primary': 'primary', 1118 'asit2.pill_primary_title': 'Main/label site of this installation', 1119 'asit2.pill_public': 'public', 1120 'asit2.pill_public_title': 'Publicly visible', 1121 'asit2.pill_private': 'private', 1122 'asit2.pill_private_title': 'Not public', 1123 'asit2.pill_noindex': 'noindex', 1124 'asit2.pill_noindex_title': 'Not indexed by search engines', 1125 'asit2.by': 'by', 1126 'asit2.t_posts': 'Number of posts', 1127 'asit2.l_posts': 'posts', 1128 'asit2.t_created': 'Created on', 1129 'asit2.l_created': 'created', 1130 'asit2.make_primary': 'Make primary', 1131 'asit2.make_primary_title': 'Make primary/main site', 1132 'asit2.make_primary_confirm': 'Set this site as the primary/main site?', 1133 'asit2.edit': 'Edit', 1134 'asit2.delete': 'Delete', 1135 'asit2.delete_confirm': 'Delete site? Only works if there are no posts.', 1136 'acom.back': 'Admin', 1137 'acom.title': 'Comment moderation', 1138 'acom.mode_for_site': 'Mode for this site:', 1139 'acom.mode_trust_a': 'comments are auto-approved. Switch to', 1140 'acom.mode_moderate_word': 'moderate', 1141 'acom.site_settings': 'site settings', 1142 'acom.mode_trust_b': 'to queue them.', 1143 'acom.mode_moderate_hint': 'new comments require approval before showing up on posts.', 1144 'acom.pending': 'Pending ({n})', 1145 'acom.nothing_waiting': 'Nothing waiting.', 1146 'acom.reply': 'reply', 1147 'acom.on': 'on', 1148 'acom.approve': 'Approve', 1149 'acom.reject': 'Reject', 1150 'acom.recent': 'Recent decisions', 1151 'acom.nothing_yet': 'Nothing yet.', 1152 'acir.title': 'Circle', 1153 'acir.back_settings': 'Settings', 1154 'acir.circles': 'Circles', 1155 'acir.settings': 'Settings', 1156 'acir.mode_off_1': 'The mode is not set to', 1157 'acir.mode_off_2': '. Turn it on under', 1158 'acir.mode_off_3': 'to show your circle feed at', 1159 'acir.mode_off_4': '. You can already set up sources below.', 1160 'acir.visibility_title': 'My visibility', 1161 'acir.all_public': 'already-public', 1162 'acir.visibility_help_1': 'Taking part in circles? This makes your', 1163 'acir.visibility_help_2': 'posts fetchable by other Klonkt sites through a signed feed', 1164 'acir.visibility_help_3': 'It is an opt-in choice, not a privacy lock: your posts stay public on your site anyway, even when this is off. To shield something, mark that post as non-public.', 1165 'acir.show_in_circles': 'Show my site in other people\'s circles', 1166 'acir.save': 'Save', 1167 'acir.add_title': 'Add a Klonkt site', 1168 'acir.add_help': 'Paste the base URL of another Klonkt site. Asymmetric: you show them, regardless of whether they show you.', 1169 'acir.url': 'URL', 1170 'acir.label': 'Label', 1171 'acir.optional': 'optional', 1172 'acir.label_ph': 'e.g. Joost Klein', 1173 'acir.add': 'Add', 1174 'acir.in_circle': 'In my circle ({n})', 1175 'acir.no_sources': 'No sources yet. Add one above.', 1176 'acir.sync_all': 'Sync all now', 1177 'acir.st_active': 'active', 1178 'acir.posts': 'posts', 1179 'acir.last': 'last', 1180 'acir.st_mismatch': 'version mismatch', 1181 'acir.mismatch_reason': 'This site runs a different Klonkt protocol version — an update is needed to federate.', 1182 'acir.st_error': 'error', 1183 'acir.st_paused': 'paused', 1184 'acir.refresh': 'Refresh', 1185 'acir.remove': 'Remove', 1186 'acir.remove_confirm': 'Remove from your circle?', 1187 'ashow.back_admin': 'Admin', 1188 'ashow.title': 'Calendar', 1189 'ashow.show_toggle': 'Show the calendar on the site', 1190 'ashow.show_toggle_hint': '(Calendar button in the bar + the calendar page)', 1191 'ashow.save': 'Save', 1192 'ashow.off': 'off', 1193 'ashow.off_notice_1': 'The calendar is currently', 1194 'ashow.off_notice_2': '— visitors see no Calendar button and the calendar page is unreachable. Turn it on to show your events.', 1195 'ashow.subscribers': 'subscriber(s) for event announcements.', 1196 'ashow.smtp_warn': '⚠ SMTP not configured — events are saved, but notify emails can only be sent once you fill in SMTP.', 1197 'ashow.f_date': 'Date', 1198 'ashow.f_time': 'Time (optional)', 1199 'ashow.f_city': 'City', 1200 'ashow.f_country': 'Country (optional)', 1201 'ashow.f_venue': 'Venue/hall (optional)', 1202 'ashow.f_ticket': 'Ticket URL (optional)', 1203 'ashow.f_notes': 'Note (optional)', 1204 'ashow.f_notes_ph': 'Support: ...', 1205 'ashow.notify_label': 'Notify subscribers by email', 1206 'ashow.smtp_required': '(SMTP required)', 1207 'ashow.add_event': '+ Add event', 1208 'ashow.del_confirm': 'Delete event??', 1209 'ashow.empty': 'No events yet.', 1210 'anews.title': 'Newsletter', 1211 'anews.confirmed': 'confirmed', 1212 'anews.pending': 'pending', 1213 'anews.unsub': 'unsubscribed', 1214 'anews.smtp_warn_1': '⚠ SMTP is not configured yet. Sign-ups are still collected, but sending is only possible once you fill in your SMTP details', 1215 'anews.smtp_warn_2': 'in', 1216 'anews.share': 'Sign-up link to share:', 1217 'anews.subject': 'Subject', 1218 'anews.subject_ph': 'New single out!', 1219 'anews.body': 'Message', 1220 'anews.body_ph': 'Write your update…', 1221 'anews.send_confirm': 'Send newsletter to {n} confirmed subscriber(s)?', 1222 'anews.send_btn': 'Send to {n} subscriber(s)', 1223 'anews.sent_heading': 'Sent', 1224 'anews.recipients': 'recipient(s)', 1225 'aupd.title': 'Updates', 1226 'aupd.back_admin': 'Admin', 1227 'aupd.version_heading': 'Version of this Klonkt', 1228 'aupd.app_version': 'App version', 1229 'aupd.current': 'Current', 1230 'aupd.current_unknown': 'unknown (not yet updated via the update button)', 1231 'aupd.latest': 'Latest', 1232 'aupd.latest_failed': 'could not fetch the latest version', 1233 'aupd.no_source': 'No update source reachable', 1234 'aupd.uptodate': 'Up to date', 1235 'aupd.update_available': 'Update available', 1236 'aupd.behind_one': '{n} commit behind', 1237 'aupd.behind_many': '{n} commits behind', 1238 'aupd.run_confirm': 'The site will be brought to the latest version and restart briefly. Continue?', 1239 'aupd.redeploy': 'Redeploy', 1240 'aupd.update_now': 'Update now', 1241 'aupd.help': 'Updating fetches the latest code and restarts this site briefly (~10s). Take your time — nothing is lost (your posts, settings and circle stay intact).', 1242 'aepk.title': 'Edit press kit', 1243 'aepk.back_admin': 'Admin', 1244 'aepk.view_epk': 'View press kit', 1245 'aepk.text_heading': 'Text', 1246 'aepk.text_help': 'The press kit (/pers) shows your site name + photo, this bio and contact, plus your most-played tracks and recent posts automatically. Leave the bio empty to use the site tagline; leave contact empty to show nothing (your login email is never shown automatically).', 1247 'aepk.bio_label': 'Press bio', 1248 'aepk.bio_ph': 'Short description of you/the project for press & bookers.', 1249 'aepk.contact_label': 'Press contact', 1250 'aepk.contact_ph': 'e.g. press@yourdomain.com or a booking link', 1251 'aepk.tracks_label': 'Tracks on the press kit', 1252 'aepk.tracks_hint': '(pick up to {n}; leave empty for the top {n} most-played automatically)', 1253 'aepk.no_tracks': 'No tracks yet — add audio first under Admin → Audio.', 1254 'aepk.untitled': '(untitled)', 1255 'aepk.save': 'Save', 1256 'ahelp.back': 'Admin', 1257 'ahelp.title': 'Help guide', 1258 'ahelp.intro': 'An explanation of every feature. Type below to search by topic or instruction.', 1259 'ahelp.search_placeholder': 'Search… (e.g. \'agenda\', \'photo\', \'circle\', \'downloads\')', 1260 'ahelp.search_aria': 'Search the help guide', 1261 'ahelp.premium': 'premium', 1262 'ahelp.empty': 'No topics found for your search.', 1263 'ahelp.s_newpost_h': 'Writing a new post', 1264 'ahelp.s_newpost_b': 'Admin → <strong>New post</strong>. Give it a title and write your content. At the bottom you choose the status: <em>draft</em> (not visible) or <em>published</em>. Drafts appear at the top of your Admin overview so you can find them again.', 1265 'ahelp.s_excerpt_h': 'Summary & Circle Preview', 1266 'ahelp.s_excerpt_b': 'The <strong>Summary & Circle Preview</strong> field (the excerpt) is the short preview text shown below a post in listings, and the summary that other sites display when they pick up your post via a <strong>Circle</strong>. Leave it empty and the start of the post is used automatically.', 1267 'ahelp.s_pin_h': 'Pinning posts / ordering', 1268 'ahelp.s_pin_b': 'In the post editor you can <strong>pin</strong> a post with a rank (1 = top). Pinned posts appear first in the timeline/grid, in order of their rank. An empty rank or 0 = not pinned.', 1269 'ahelp.s_schedule_h': 'Scheduling & fans only', 1270 'ahelp.s_schedule_b': 'In the editor you can set a <strong>publish date</strong> in the future — the post then appears automatically at that moment. With <strong>Fans only</strong>, visitors who are not logged in see only a teaser + login invitation; logged-in fans see everything.', 1271 'ahelp.s_images_h': 'Images in posts', 1272 'ahelp.s_images_b': 'Images in the text and the cover image are always shown in <strong>full</strong> at full width (not cropped), at their natural height.', 1273 'ahelp.s_audio_h': 'Adding audio & tracks', 1274 'ahelp.s_audio_b': 'Admin → <strong>Audio</strong>. Upload a file or add a <em>link-only</em> track (no upload, just "open in" links). For each track you fill in the title, artist, cover and, optionally, album/position. In a post you show a track with the shortcode <code>[[track:id]]</code>, an album with <code>[[album:Name]]</code>, a playlist with <code>[[playlist:id]]</code>.', 1275 'ahelp.s_credit_h': 'Credit, licence & "open in"', 1276 'ahelp.s_credit_b': 'For each track you can set an <strong>owner/credit</strong> (with a © button) and a <strong>licence</strong> — these are also written into the mp3 metadata. The <strong>open-in</strong> fields (Spotify / YouTube / SoundCloud) add buttons to open the track on those platforms.', 1277 'ahelp.s_downloads_h': 'Downloads', 1278 'ahelp.s_downloads_b': 'Mark a track as <strong>downloadable</strong> in Admin → Audio (⬇ button). Visitors find them on the <strong>/downloads</strong> page and leave their email to receive the file (it goes onto your mailing list). Want downloads prominent in the feed? Create a regular post with the slug <code>downloads</code> and pin it.', 1279 'ahelp.s_albums_h': 'Albums & playlists', 1280 'ahelp.s_albums_b': 'Give tracks the same <strong>album</strong> + a <strong>position</strong> to form an album. You create playlists in Admin → <strong>Playlists</strong>. You show both in a post with <code>[[album:Name]]</code> or <code>[[playlist:id]]</code>.', 1281 'ahelp.s_agenda_h': 'Agenda / events', 1282 'ahelp.s_agenda_b': 'Admin → <strong>Agenda</strong>. Turn on <strong>"Show agenda on the site"</strong> at the top — then the Agenda button appears in the bar and the agenda page becomes reachable. Add events (date, town, venue, tickets). Visitors can sign up (separately from the newsletter) for a heads-up about a new event.', 1283 'ahelp.s_presskit_h': 'Press kit', 1284 'ahelp.s_presskit_b': 'A shareable press page at <strong>/pers</strong>. Edit it via the <strong>✎ Edit</strong> button on that page itself (only you see it). Set a short press bio + contact, and choose <strong>up to 5 tracks</strong> to be shown (or leave empty = automatically the top 5 most-played).', 1285 'ahelp.s_circles_h': 'Circles (federation)', 1286 'ahelp.s_circles_b': 'In Admin → Settings you set your site to <strong>Circle</strong> mode. After that you add other Klonkt sites in Admin → <strong>Circle</strong>; their public posts then appear in your <strong>/cirkel</strong> feed. What others see of <em>you</em> is the title + the "Summary & Circle Preview". Hubs do not take part in circles (solo sites only).', 1287 'ahelp.s_stats_h': 'Statistics', 1288 'ahelp.s_stats_b': 'Admin → <strong>Statistics</strong>. Measured cookie-free. <strong>Visitor-days</strong> = unique visitors per day, added up (not a number of people). <strong>Views</strong> = home/feed and post loads. Admin visits and bots do not count. Good for trends; take absolute numbers with a grain of salt.', 1289 'ahelp.s_newsletter_h': 'Newsletter', 1290 'ahelp.s_newsletter_b': 'Admin → <strong>Newsletter</strong>: compose a message and send it to your confirmed subscribers. Visitors sign up via the footer or <strong>/nieuwsbrief</strong>. Sending requires that email (SMTP) is configured.', 1291 'ahelp.s_linkbio_h': 'Link-in-bio', 1292 'ahelp.s_linkbio_b': 'A Linktree-style page at <strong>/links</strong> with your profile links. You can see the clicks per link in Statistics.', 1293 'ahelp.s_embed_h': 'Embeddable player', 1294 'ahelp.s_embed_b': 'Admin → Audio shows a copyable <code><iframe></code> code (<strong>/embed</strong>) that lets you embed your player on another website.', 1295 'ahelp.s_appearance_h': 'Appearance (theme, photo, accent)', 1296 'ahelp.s_appearance_b': 'Admin → <strong>Appearance</strong>: set your site name, tagline, profile photo, accent colour and colour palette, and the default feed view (Timeline or Grid).', 1297 'ahelp.s_tenancy_h': 'Solo / Hub mode', 1298 'ahelp.s_tenancy_b': 'Admin → <strong>Settings</strong>: <em>Solo</em> = one site (you). <em>Hub</em> = a company/label site with multiple creators, each with their own Klonkt under <code>/user/<name></code>. <em>Circle</em> = solo + federation with other sites.', 1299 'ahelp.s_premium_h': 'Premium / Patreon', 1300 'ahelp.s_premium_b': 'You unlock premium features (Hub, Statistics, Agenda, Downloads, Press kit, Newsletter, Link-in-bio, Embed, fan login) in Admin → Settings by connecting Patreon ($10 lifetime). Updates and the core app always stay free.', 1301 'ahelp.s_google_h': 'Google login (visitors)', 1302 'ahelp.s_google_b': 'Admin → Settings → <strong>Google</strong>. Optional: let visitors/fans log in with Google to comment. You manage with your password — Google never grants admin rights. Enter your own Google client details (per site).', 1303 'ahelp.s_updates_h': 'Updates', 1304 'ahelp.s_updates_b': 'Admin → <strong>Updates</strong> (god only) shows the current version and whether a newer one is available. With "Update now" you fetch the latest version.', 205 1305 }, 206 1306 de: { … … 296 1396 'search.empty': 'Nichts gefunden.', 'search.in_post': 'im Beitrag →', 297 1397 'like.login_title': 'Zum Liken anmelden', 1398 // === Verwaltungs-Unterseiten === 1399 'aset.title': 'Einstellungen', 1400 'aset.back_admin': 'Verwaltung', 1401 'aset.mode': 'Modus', 1402 'aset.mode_help': 'Legt fest, wie diese Installation funktioniert. Das Wechseln ist sicher: Es wird nichts gelöscht — Solo blendet lediglich die Multi-Site-Funktionen aus und zeigt deine primäre Site.', 1403 'aset.solo': 'Solo', 1404 'aset.solo_title': 'eine Site (deine).', 1405 'aset.solo_desc': 'Kein Benutzerverzeichnis, kein Site-Wechsel. Besucher können sich aber mit Google anmelden, um zu kommentieren.', 1406 'aset.hub': 'Hub', 1407 'aset.hub_title': 'Unternehmens-Site mit', 1408 'aset.premium_badge': 'Premium', 1409 'aset.hub_desc': 'Du (Admin) weist Benutzern einen eigenen Klonkt Hub zu. Geschlossener Kreis: keine offene Registrierung.', 1410 'aset.hub_locked_note': 'Erfordert Premium — verbinde Patreon auf dieser Seite.', 1411 'aset.circle': 'Kreise', 1412 'aset.circle_title': 'Solo + Föderation.', 1413 'aset.circle_desc': 'Eine eigene Site, die die öffentlichen Beiträge anderer Klonkt-Sites anzeigt. Asymmetrisch: Du bestimmst, wer in deinem Kreis ist.', 1414 'aset.save': 'Speichern', 1415 'aset.hub_home': 'Hub-Startseite', 1416 'aset.hub_home_help_1': 'Die öffentliche Hub-Seite', 1417 'aset.hub_home_help_2': 'ist generisch — sie gehört keinem einzelnen Benutzer. Hier legst du Name und Intro fest. Dein eigener Klonkt Hub bleibt separat unter', 1418 'aset.name': 'Name', 1419 'aset.name_ph': 'z. B. Studio Noord', 1420 'aset.tagline': 'Tagline', 1421 'aset.tagline_ph': 'z. B. Unabhängiges Musiklabel', 1422 'aset.intro': 'Intro', 1423 'aset.intro_ph': 'Kurzer Introtext unter dem Titel.', 1424 'aset.hero_image': 'Hero-Bild (URL)', 1425 'aset.hero_image_hint': 'optional; Hintergrund des Heros', 1426 'aset.hero_upload': '…oder lade ein Bild hoch', 1427 'aset.hero_upload_hint': 'jpg/png/webp/gif, max. 5 MB; ersetzt die URL oben', 1428 'aset.hero_overlay': 'Dunkles Overlay', 1429 'aset.hero_overlay_hint': 'verdunkelt das Hero, damit der Text lesbar bleibt', 1430 'aset.preview_overlay': 'Vorschau (mit Overlay):', 1431 'aset.hero_preview_alt': 'Hero-Vorschau', 1432 'aset.your_circle': 'Dein Kreis', 1433 'aset.your_circle_help': 'Verwalte, welche anderen Klonkt-Sites du in deinem Kreis anzeigst und ob deine Site in den Kreisen anderer erscheinen darf. Asymmetrisch: Du bestimmst, wem du folgst.', 1434 'aset.manage_circle': 'Deinen Kreis verwalten', 1435 'aset.premium': 'Premium (Patreon)', 1436 'aset.premium_help_1': 'Schalte die Premium-Module (Hub-Modus, Statistiken, Fan-Login, Prutter-DMs) mit deiner', 1437 'aset.premium_lifetime': '$10-Lifetime', 1438 'aset.premium_help_2': 'Patreon-Unterstützung frei. Die App und alle Updates bleiben kostenlos.', 1439 'aset.premium_active': 'Premium aktiv.', 1440 'aset.lifetime_support': 'Lifetime-Unterstützung:', 1441 'aset.patreon_disconnect': 'Patreon trennen', 1442 'aset.patreon_no_lifetime': 'Patreon verbunden, aber noch keine $10 Lifetime', 1443 'aset.now': 'derzeit', 1444 'aset.patreon_support_again': 'Unterstütze die Kampagne und verbinde erneut.', 1445 'aset.patreon_reconnect': 'Erneut verbinden', 1446 'aset.patreon_not_connected': 'Noch nicht verbunden.', 1447 'aset.patreon_connect': 'Patreon verbinden', 1448 'aset.google_login': 'Google-Login (Besucher)', 1449 'aset.google_login_help': 'Lass Besucher sich mit Google anmelden, um zu kommentieren — mit deinem eigenen Google-Cloud-Client.', 1450 'aset.status_set': 'Status: eingerichtet', 1451 'aset.not_set_yet': 'Noch nicht eingerichtet.', 1452 'aset.google_login_setup': 'Google-Login einrichten', 1453 'aset.newsletter': 'Newsletter', 1454 'aset.newsletter_help_1': 'Zeige ein', 1455 'aset.newsletter_footer_field': 'Anmeldefeld in der Fußzeile', 1456 'aset.newsletter_help_2': 'deiner Site, damit sich Besucher auf jeder Seite eintragen können. (Die vollständige Anmeldeseite bleibt unter', 1457 'aset.newsletter_show_footer': 'Anmeldefeld in der Fußzeile anzeigen', 1458 'aset.email_smtp': 'E-Mail (SMTP)', 1459 'aset.smtp_help_1': 'Erforderlich, um den', 1460 'aset.smtp_help_newsletter': 'Newsletter zu versenden', 1461 'aset.smtp_help_2': ',', 1462 'aset.smtp_help_notify': 'Show-Notify', 1463 'aset.smtp_help_3': '-Mails zu senden und das Zurücksetzen des Passworts per Mail zu ermöglichen. Gib die Daten deines Mailanbieters ein (z. B. deine Hosting-Mail, ein Gmail-App-Passwort, Brevo, Mailgun…).', 1464 'aset.via_env': 'über .env', 1465 'aset.smtp_not_set': 'Noch nicht eingerichtet — Versenden funktioniert noch nicht.', 1466 'aset.smtp_host': 'SMTP-Host', 1467 'aset.smtp_port': 'Port', 1468 'aset.smtp_port_hint': '587 (STARTTLS) oder 465 (SSL)', 1469 'aset.smtp_user': 'Benutzername', 1470 'aset.smtp_pass': 'Passwort', 1471 'aset.smtp_pass_set_hint': 'eingerichtet; leer lassen = unverändert', 1472 'aset.smtp_pass_ph_set': '•••••••• (eingerichtet)', 1473 'aset.smtp_pass_ph': 'App-Passwort', 1474 'aset.smtp_from': 'Absender', 1475 'aset.smtp_from_hint': 'optional; Standard = Benutzername', 1476 'aset.smtp_from_ph': 'Dein Name <du@deinanbieter.de>', 1477 'aset.smtp_save': 'SMTP speichern', 1478 'aset.clear': 'Löschen', 1479 'aset.test_mail_to': 'Testmail senden an', 1480 'aset.send_test_mail': 'Testmail senden', 1481 'asite.back_admin': 'Verwaltung', 1482 'asite.title_new': 'Neue Seite', 1483 'asite.title_edit': 'Erscheinungsbild', 1484 'asite.identity': 'Identität', 1485 'asite.slug': 'Slug (URL)', 1486 'asite.slug_fixed': '(fest)', 1487 'asite.slug_placeholder': 'deinslug', 1488 'asite.field_title': 'Titel', 1489 'asite.field_title_hint': '— wird im Kopf und als Anzeigename gezeigt', 1490 'asite.owner': 'Eigentümer', 1491 'asite.owner_hint': '— wer dieses Klonkt selbst verwalten darf', 1492 'asite.owner_god_suffix': ' (god)', 1493 'asite.tagline': 'Tagline', 1494 'asite.tagline_hint': '— kurzer Einzeiler', 1495 'asite.bio': 'Bio / Beschreibung', 1496 'asite.bio_hint': '— wird im Profilkopf gezeigt und für SEO verwendet', 1497 'asite.profile_photo': 'Profilfoto', 1498 'asite.photo_url_placeholder': '/media/avatars/foo.jpg oder https://…', 1499 'asite.photo_upload': '📷 Hochladen', 1500 'asite.photo_clear': 'Entfernen', 1501 'asite.language': 'Sprache (ISO-Code)', 1502 'asite.profile_enabled': 'Profilkopf unter der Navigation anzeigen', 1503 'asite.appearance': 'Gestaltung', 1504 'asite.accent_color': 'Akzentfarbe', 1505 'asite.theme_default': 'Standardthema für neue Besucher', 1506 'asite.theme_auto': 'Auto (Geräteeinstellung folgen)', 1507 'asite.theme_light': 'Hell', 1508 'asite.theme_dark': 'Dunkel', 1509 'asite.palette': 'Palette', 1510 'asite.behavior': 'Verhalten', 1511 'asite.is_public': 'Öffentliche Seite (abwählen für einen geschlossenen Kreis)', 1512 'asite.robots_index': 'Suchmaschinen dürfen indexieren (sitemap.xml ist ausgeblendet, wenn aus)', 1513 'asite.require_login_comment': 'Anmeldung zum Kommentieren erforderlich', 1514 'asite.comment_moderation': 'Kommentar-Moderation', 1515 'asite.moderation_trust': 'Vertrauen (automatisch freigeben)', 1516 'asite.moderation_moderate': 'Moderieren (zuerst in die Warteschlange)', 1517 'asite.enable_audio': 'Audioplayer + Embeds aktivieren', 1518 'asite.enable_prutter': 'Prutter (DMs zwischen Mitgliedern) aktivieren', 1519 'asite.enable_prutter_hint': '— Hub-Funktion, Premium', 1520 'asite.links': 'Social- / Streaming-Links', 1521 'asite.links_hint': 'Werden als Marken-Icons im Profilkopf gezeigt. Füge so viele hinzu, wie du möchtest.', 1522 'asite.link_add': '+ Link hinzufügen', 1523 'asite.feed_view': 'Feed-Anzeige', 1524 'asite.feed_default': 'Standardansicht für die Startseite', 1525 'asite.feed_timeline': 'Zeitstrahl (chronologische Liste)', 1526 'asite.feed_grid': 'Raster (Karten)', 1527 'asite.feed_switch': 'Umschalter Zeitstrahl ↔ Raster über dem Feed anzeigen', 1528 'asite.show_search': 'Suchschaltfläche in der Navigation anzeigen', 1529 'asite.show_archive': 'Archiv-Link in der Navigation anzeigen', 1530 'asite.seo': 'SEO & Social', 1531 'asite.seo_pointer': 'Titelvorlage, Canonical, Teilbild, Verifizierungs-Metas und mehr befinden sich jetzt auf einer eigenen Seite:', 1532 'asite.seo_link': '🔎 SEO & Auffindbarkeit', 1533 'asite.custom_legend': 'Eigenes CSS & HTML', 1534 'asite.optional': '(optional)', 1535 'asite.custom_css': 'Eigenes CSS (eingefügt als <style> im <head>)', 1536 'asite.custom_head': 'Eigenes <head>-HTML (Analytics, zusätzliche Metas)', 1537 'asite.custom_foot': 'Eigenes Footer-HTML', 1538 'asite.submit_create': 'Seite erstellen', 1539 'asite.submit_save': 'Änderungen speichern', 1540 'aseo.back': 'Verwaltung', 1541 'aseo.title': 'SEO & Auffindbarkeit', 1542 'aseo.tagline_pre': 'Erweiterte SEO von', 1543 'aseo.tagline_post': '— wie deine Seite in Suchmaschinen und beim Teilen in sozialen Medien erscheint.', 1544 'aseo.index_legend': 'Indexierung', 1545 'aseo.index_label': 'Suchmaschinen dürfen diese Seite indexieren', 1546 'aseo.index_hint_pre': 'aus =', 1547 'aseo.index_hint_post': '+ sitemap.xml verborgen', 1548 'aseo.title_legend': 'Titel & Beschreibung', 1549 'aseo.title_template': 'Titelvorlage', 1550 'aseo.title_template_hint_pre': 'verwende', 1551 'aseo.title_template_hint_and': 'und', 1552 'aseo.default_desc': 'Standardbeschreibung', 1553 'aseo.default_desc_hint': 'Meta-Description / og:description, wenn eine Seite keine hat', 1554 'aseo.default_desc_ph': 'Kurze Beschreibung deiner Seite (max. ~160 Zeichen funktionieren am besten)', 1555 'aseo.canonical': 'Kanonische Basis-URL', 1556 'aseo.canonical_hint': 'die Produktions-HTTPS-URL, verhindert Strafen für doppelte Inhalte', 1557 'aseo.author': 'Autor', 1558 'aseo.author_hint': 'Meta-Author-Tag', 1559 'aseo.author_ph': 'Dein Name', 1560 'aseo.social_legend': 'Teilen in sozialen Medien', 1561 'aseo.og_image': 'Standard-Teilbild (URL)', 1562 'aseo.og_image_hint': 'og:image / Twitter-Card; ~1200×630px', 1563 'aseo.og_locale': 'Sprach-Locale', 1564 'aseo.og_locale_hint_pre': 'og:locale, z. B.', 1565 'aseo.og_locale_hint_or': 'oder', 1566 'aseo.twitter': 'Twitter-/X-Handle', 1567 'aseo.twitter_hint': 'mit @', 1568 'aseo.fb_app': 'Facebook App-ID', 1569 'aseo.fb_app_hint': 'fb:app_id (optional)', 1570 'aseo.publisher_legend': 'Herausgeber (JSON-LD / Rich Results)', 1571 'aseo.type': 'Typ', 1572 'aseo.type_person': 'Person', 1573 'aseo.type_org': 'Organisation / Unternehmen', 1574 'aseo.publisher_name': 'Name', 1575 'aseo.publisher_name_hint': 'fällt auf den Seitentitel zurück', 1576 'aseo.publisher_url': 'URL', 1577 'aseo.publisher_logo': 'Logo (URL)', 1578 'aseo.verify_legend': 'Suchmaschinen-Verifizierung', 1579 'aseo.verify_google': 'Google-Site-Verifizierung', 1580 'aseo.verify_bing': 'Bing', 1581 'aseo.verify_bing_hint': 'msvalidate.01', 1582 'aseo.verify_pinterest': 'Pinterest', 1583 'aseo.verify_pinterest_hint': 'p:domain_verify', 1584 'aseo.verify_yandex': 'Yandex', 1585 'aseo.save': 'SEO speichern', 1586 'agoog.title': 'Google-Anmeldung', 1587 'agoog.back': 'Einstellungen', 1588 'agoog.card_title': 'Google-Anmeldung (Besucher)', 1589 'agoog.card_help_pre': 'Lass Besucher sich mit Google anmelden, um zu kommentieren — mit deinem', 1590 'agoog.card_help_own': 'eigenen', 1591 'agoog.card_help_mid': 'Google-Cloud-Client. Erstelle einen unter', 1592 'agoog.card_help_post': '(OAuth-Client-ID, Typ „Web application“).', 1593 'agoog.status_set': 'Status: eingerichtet ✓', 1594 'agoog.status_unset': 'Noch nicht eingerichtet.', 1595 'agoog.guide_summary': 'Wie richte ich das ein? (Schritt für Schritt)', 1596 'agoog.step1_pre': 'Gehe zur', 1597 'agoog.step1_post': 'und erstelle (oder wähle) ein Projekt.', 1598 'agoog.step2_usertype': 'User type', 1599 'agoog.step2_scopes_pre': 'Füge die Scopes', 1600 'agoog.step2_scopes_and': 'und', 1601 'agoog.step2_scopes_post': 'hinzu.', 1602 'agoog.step2_testmode': '(Im Testmodus: füge dich selbst als Testnutzer hinzu.)', 1603 'agoog.step3_type': 'Typ', 1604 'agoog.step4_pre': 'Unter', 1605 'agoog.step4_post': ': füge die Redirect-URI unten ein (muss exakt übereinstimmen).', 1606 'agoog.step5_pre': 'Kopiere die', 1607 'agoog.step5_post': 'die du erhältst, in die Felder unten und klicke auf Speichern.', 1608 'agoog.note_label': 'Achtung:', 1609 'agoog.note_pre': 'du musst', 1610 'agoog.note_noapi': 'keine APIs aktivieren', 1611 'agoog.note_mid': '— „Google Cloud APIs“, People API usw. sind', 1612 'agoog.note_not': 'nicht', 1613 'agoog.note_post': 'erforderlich. Die Anmeldung läuft über OpenID Connect (nur der Zustimmungsbildschirm + der OAuth-Client).', 1614 'agoog.redirect_label': 'Redirect-URI — kopiere diese und füge sie in Google ein', 1615 'agoog.rstep1_pre': 'Gehe zur', 1616 'agoog.rstep2': 'Öffne deine OAuth-Client-ID (Typ „Web application“).', 1617 'agoog.rstep3_pre': 'Unter', 1618 'agoog.rstep3_mid': 'klicke auf', 1619 'agoog.rstep3_post': 'und füge die obige Zeile ein.', 1620 'agoog.rstep4_pre': 'Klicke auf', 1621 'agoog.rstep4_post': 'und warte ~1 Minute.', 1622 'agoog.warn_pre': 'Muss', 1623 'agoog.warn_exact': 'exakt', 1624 'agoog.warn_mid1': 'stimmen:', 1625 'agoog.warn_nohttp': 'kein', 1626 'agoog.warn_mid2': ', richtige Domain und', 1627 'agoog.warn_noslash': 'kein', 1628 'agoog.warn_mid3': 'Schrägstrich am Ende. Andernfalls bekommst du', 1629 'agoog.warn_error': '„Error 400: redirect_uri_mismatch“', 1630 'agoog.set_baseurl_pre': 'Lege zuerst', 1631 'agoog.set_baseurl_post': 'fest — daraus wird die Redirect-URI abgeleitet.', 1632 'agoog.client_id': 'Client ID', 1633 'agoog.client_secret': 'Client Secret', 1634 'agoog.client_secret_set': 'eingerichtet; leer lassen = unverändert', 1635 'agoog.secret_ph_set': '•••••••• (eingerichtet)', 1636 'agoog.save': 'Speichern', 1637 'agoog.disconnect': 'Trennen', 1638 'agoog.premium_locked': 'Die Premium-Ebene ist ohne Patreon aktiv — der Google-Button bleibt verborgen, bis Premium freigeschaltet ist.', 1639 'aaud.title': 'Audio-Tracks', 1640 'aaud.tagline_pre': 'MP3s auf Site-Ebene. Verwende', 1641 'aaud.tagline_post': 'in einem Beitrag, um eine Wiedergabe-Schaltfläche einzufügen.', 1642 'aaud.upload': 'Upload', 1643 'aaud.artist': 'Künstler', 1644 'aaud.album': 'Album', 1645 'aaud.applied_all': '(auf alle Dateien angewendet)', 1646 'aaud.optional': 'Optional', 1647 'aaud.cover': 'Cover', 1648 'aaud.cover_hint': '(optional, auf alle Dateien angewendet — jpg/png/webp/gif, max. 5 MB)', 1649 'aaud.choose_cover': 'Cover wählen', 1650 'aaud.no_file': 'Keine Datei gewählt', 1651 'aaud.drag_here': 'Audio hierher ziehen', 1652 'aaud.or_click': 'oder klicken, um Dateien zu wählen', 1653 'aaud.start_upload': 'Upload starten', 1654 'aaud.clear_list': 'Liste leeren', 1655 'aaud.tracks': 'Tracks', 1656 'aaud.add_link_track': 'Track ohne Audio', 1657 'aaud.add_link_track_title': 'Ein Track ohne Audiodatei — nur Titel + Öffnen-in-Links', 1658 'aaud.no_tracks': 'Noch keine Tracks. Lade oben einen hoch.', 1659 'aaud.untitled': '(ohne Titel)', 1660 'aaud.copy_click': 'Zum Kopieren klicken', 1661 'aaud.play': 'Abspielen', 1662 'aaud.pause': 'Pausieren', 1663 'aaud.edit': 'Bearbeiten', 1664 'aaud.delete': 'Löschen', 1665 'aaud.delete_confirm': 'Track löschen?', 1666 'aaud.dl_on': 'Download-für-E-Mail ist AN — zum Ausschalten klicken', 1667 'aaud.dl_off': 'Download-für-E-Mail ist aus — zum Einschalten klicken', 1668 'aaud.embed_player': 'Einbettbarer Player', 1669 'aaud.embed_hint': 'Füge diesen Code auf deiner eigenen Website/deinem Blog ein, um deine Musik mit diesem Player einzubetten:', 1670 'aaud.preview_player': 'Player-Vorschau öffnen', 1671 'aaud.st_queued': 'Warten', 1672 'aaud.st_uploading': 'Wird hochgeladen…', 1673 'aaud.st_transcoding': 'Wird konvertiert…', 1674 'aaud.st_done': 'Fertig', 1675 'aaud.st_error': 'Fehler', 1676 'aaud.err_unexpected': 'Unerwartete Serverantwort', 1677 'aaud.failed': 'Fehlgeschlagen', 1678 'aaud.copied': 'kopiert', 1679 'aaud.new_track': 'Neuer Track', 1680 'aaud.create_failed': 'Track konnte nicht erstellt werden', 1681 'aaud.editor_not_loaded': 'Track-Editor nicht geladen', 1682 'aaud.change_failed': 'Änderung fehlgeschlagen', 1683 'astat.title': 'Statistiken', 1684 'astat.intro': 'Cookiefrei gemessen — keine Tracking-Cookies, kein Zustimmungsbanner. Besucher werden pro Tag über einen täglich rotierenden, anonymen Hash gezählt (IP/Browser werden nicht gespeichert). Deine eigenen Admin-Besuche und bekannte Bots/Crawler werden nicht mitgezählt.', 1685 'astat.visitor_days': 'Besuchertage ({n}T)', 1686 'astat.pageviews_days': 'Aufrufe ({n}T)', 1687 'astat.plays_total': 'Wiedergaben (gesamt)', 1688 'astat.postviews_total': 'Beitragsaufrufe (gesamt)', 1689 'astat.alltime_pre': 'Gesamt:', 1690 'astat.alltime_mid': 'Aufrufe', 1691 'astat.alltime_post': 'Besuchertage.', 1692 'astat.help_summary': 'Was bedeuten diese Zahlen?', 1693 'astat.help_vd_term': 'Besuchertage', 1694 'astat.help_vd_a': 'die Anzahl eindeutiger Besucher', 1695 'astat.help_vd_em': 'pro Tag, zusammengezählt', 1696 'astat.help_vd_b': '. Eine Person, die an 5 Tagen vorbeischaut = 5 Besuchertage. Ohne Cookies kann nicht über Tage hinweg gezählt werden, daher ist dies keine Anzahl eindeutiger Personen — die tatsächliche Personenzahl liegt (oft deutlich) niedriger.', 1697 'astat.help_pv_term': 'Aufrufe', 1698 'astat.help_pv': 'wie oft die Startseite/der Feed oder ein Beitrag geladen wurde (auch beim Klicken innerhalb der Site). Andere Seiten (Termine, Downloads, Links) werden hier nicht mitgezählt.', 1699 'astat.help_plays_term': 'Wiedergaben', 1700 'astat.help_plays': 'Gesamtzahl, wie oft ein Track gestartet wurde.', 1701 'astat.help_postviews_term': 'Beitragsaufrufe', 1702 'astat.help_postviews': 'gesamt über alle Beiträge zusammen.', 1703 'astat.help_footer': 'Admin-Besuche und bekannte Bots/Crawler werden übersprungen. Die rohe IP wird niemals gespeichert. Gut für Trends; nimm absolute Zahlen mit Vorsicht.', 1704 'astat.period': 'Zeitraum:', 1705 'astat.last_n_days': 'Letzte {n} Tage', 1706 'astat.lg_visitor_days': 'Besuchertage', 1707 'astat.lg_pageviews': 'Aufrufe', 1708 'astat.bar_title': '{day} — {pv} Aufrufe, {vd} Besuchertage', 1709 'astat.top_posts': 'Beliebteste Beiträge', 1710 'astat.no_views': 'Noch keine Aufrufe.', 1711 'astat.most_played': 'Meistgehört', 1712 'astat.no_plays': 'Noch keine Wiedergaben.', 1713 'astat.sources': 'Quellen (woher Besucher kommen)', 1714 'astat.linkbio_clicks': 'Link-in-Bio-Klicks', 1715 'apl.title': 'Playlists', 1716 'apl.tagline_pre': 'Kanonische Playlists. Bearbeite eine Playlist hier und die Änderungen wirken sich auf alle Beiträge aus, die sie verwenden, über', 1717 'apl.tagline_post': '.', 1718 'apl.new_playlist': 'Neue Playlist', 1719 'apl.none': 'Noch keine Playlists.', 1720 'apl.none_sub': 'Erstelle eine über die Schaltfläche oben oder über die 📃-Schaltfläche im Beitrags-Editor.', 1721 'apl.pill_playlist': 'Playlist', 1722 'apl.pill_album': 'Album', 1723 'apl.track': 'Track', 1724 'apl.tracks': 'Tracks', 1725 'apl.copy_click': 'Zum Kopieren klicken', 1726 'apl.edit': 'Bearbeiten', 1727 'apl.delete': 'Löschen', 1728 'apl.copied': 'kopiert', 1729 'apl.delete_confirm': 'Playlist "{title}" löschen? Beiträge, die diese Playlist einbetten, zeigen ab jetzt einen Platzhalter.', 1730 'apl.delete_failed': 'Löschen fehlgeschlagen', 1731 'ausr.back': 'Verwaltung', 1732 'ausr.title': 'Benutzer', 1733 'ausr.tagline_a': 'Benutzer, Rollen und Löschungen verwalten. Rolle', 1734 'ausr.tagline_b': '= alles ansehen (inkl. Verwaltung), nichts ändern — praktisch für Demos.', 1735 'ausr.empty': 'Keine Benutzer.', 1736 'ausr.you': 'du', 1737 'ausr.t_sites': 'Seiten', 1738 'ausr.t_posts': 'Beiträge', 1739 'ausr.t_joined': 'Registriert am', 1740 'ausr.l_sites': 'Seiten', 1741 'ausr.l_posts': 'Beiträge', 1742 'ausr.l_joined': 'dabei seit', 1743 'ausr.new_klonkt': 'Neues Klonkt für diesen Benutzer', 1744 'ausr.new_klonkt_for': 'Neues Klonkt für {name}', 1745 'ausr.role_label': 'Rolle', 1746 'ausr.role_kijker': 'Betrachter', 1747 'ausr.role_member': 'Mitglied', 1748 'ausr.role_admin': 'Admin', 1749 'ausr.role_god': 'God', 1750 'ausr.delete': 'Löschen', 1751 'ausr.del_warn': 'Dies löscht auch ihre Seite + {n} Beitrag/Beiträge.', 1752 'ausr.del_confirm': 'Benutzer {name} löschen?', 1753 'ausr.del_undo': 'Dies kann nicht rückgängig gemacht werden.', 1754 'asit2.back': 'Verwaltung', 1755 'asit2.title': 'Seiten', 1756 'asit2.tagline': 'Alle Seiten dieser Installation verwalten.', 1757 'asit2.new_site': 'Neue Seite', 1758 'asit2.empty': 'Noch keine Seiten.', 1759 'asit2.empty_sub': 'Erstelle eine über die Schaltfläche oben.', 1760 'asit2.pill_primary': 'primär', 1761 'asit2.pill_primary_title': 'Haupt-/Label-Seite dieser Installation', 1762 'asit2.pill_public': 'öffentlich', 1763 'asit2.pill_public_title': 'Öffentlich sichtbar', 1764 'asit2.pill_private': 'privat', 1765 'asit2.pill_private_title': 'Nicht öffentlich', 1766 'asit2.pill_noindex': 'noindex', 1767 'asit2.pill_noindex_title': 'Nicht von Suchmaschinen indexiert', 1768 'asit2.by': 'von', 1769 'asit2.t_posts': 'Anzahl Beiträge', 1770 'asit2.l_posts': 'Beiträge', 1771 'asit2.t_created': 'Erstellt am', 1772 'asit2.l_created': 'erstellt', 1773 'asit2.make_primary': 'Als primär festlegen', 1774 'asit2.make_primary_title': 'Als primäre/Haupt-Seite festlegen', 1775 'asit2.make_primary_confirm': 'Diese Seite als primäre/Haupt-Seite festlegen?', 1776 'asit2.edit': 'Bearbeiten', 1777 'asit2.delete': 'Löschen', 1778 'asit2.delete_confirm': 'Seite löschen? Funktioniert nur, wenn keine Beiträge vorhanden sind.', 1779 'acom.back': 'Verwaltung', 1780 'acom.title': 'Kommentar-Moderation', 1781 'acom.mode_for_site': 'Modus für diese Seite:', 1782 'acom.mode_trust_a': 'Kommentare werden automatisch freigegeben. Wechsle zu', 1783 'acom.mode_moderate_word': 'Moderieren', 1784 'acom.site_settings': 'Seiteneinstellungen', 1785 'acom.mode_trust_b': 'um sie in die Warteschlange zu stellen.', 1786 'acom.mode_moderate_hint': 'Neue Kommentare müssen freigegeben werden, bevor sie bei Beiträgen erscheinen.', 1787 'acom.pending': 'Ausstehend ({n})', 1788 'acom.nothing_waiting': 'Nichts in der Warteschlange.', 1789 'acom.reply': 'Antwort', 1790 'acom.on': 'zu', 1791 'acom.approve': 'Freigeben', 1792 'acom.reject': 'Ablehnen', 1793 'acom.recent': 'Letzte Entscheidungen', 1794 'acom.nothing_yet': 'Noch nichts.', 1795 'acir.title': 'Zirkel', 1796 'acir.back_settings': 'Einstellungen', 1797 'acir.circles': 'Zirkel', 1798 'acir.settings': 'Einstellungen', 1799 'acir.mode_off_1': 'Der Modus steht nicht auf', 1800 'acir.mode_off_2': '. Schalte ihn unter', 1801 'acir.mode_off_3': 'ein, um deinen Zirkel-Feed anzuzeigen auf', 1802 'acir.mode_off_4': '. Du kannst unten aber schon Quellen vorbereiten.', 1803 'acir.visibility_title': 'Meine Sichtbarkeit', 1804 'acir.all_public': 'bereits öffentlichen', 1805 'acir.visibility_help_1': 'Machst du bei Zirkeln mit? Damit werden deine', 1806 'acir.visibility_help_2': 'Beiträge für andere Klonkt-Seiten über einen signierten Feed abrufbar', 1807 'acir.visibility_help_3': 'Es ist eine Teilnahme-Entscheidung, kein Datenschutz-Schloss: deine Beiträge bleiben ohnehin öffentlich auf deiner Seite, auch wenn dies aus ist. Möchtest du etwas abschirmen, mach den Beitrag nicht-öffentlich.', 1808 'acir.show_in_circles': 'Meine Seite in den Zirkeln anderer anzeigen', 1809 'acir.save': 'Speichern', 1810 'acir.add_title': 'Klonkt-Seite hinzufügen', 1811 'acir.add_help': 'Füge die Basis-URL einer anderen Klonkt-Seite ein. Asymmetrisch: du zeigst sie, unabhängig davon, ob sie dich zeigen.', 1812 'acir.url': 'URL', 1813 'acir.label': 'Bezeichnung', 1814 'acir.optional': 'optional', 1815 'acir.label_ph': 'z. B. Joost Klein', 1816 'acir.add': 'Hinzufügen', 1817 'acir.in_circle': 'In meinem Zirkel ({n})', 1818 'acir.no_sources': 'Noch keine Quellen. Füge oben eine hinzu.', 1819 'acir.sync_all': 'Jetzt alle synchronisieren', 1820 'acir.st_active': 'aktiv', 1821 'acir.posts': 'Beiträge', 1822 'acir.last': 'zuletzt', 1823 'acir.st_mismatch': 'Versions-Unterschied', 1824 'acir.mismatch_reason': 'Diese Seite läuft mit einer anderen Klonkt-Protokollversion — ein Update ist nötig, um zu föderieren.', 1825 'acir.st_error': 'Fehler', 1826 'acir.st_paused': 'pausiert', 1827 'acir.refresh': 'Aktualisieren', 1828 'acir.remove': 'Entfernen', 1829 'acir.remove_confirm': 'Aus deinem Zirkel entfernen?', 1830 'ashow.back_admin': 'Verwaltung', 1831 'ashow.title': 'Termine', 1832 'ashow.show_toggle': 'Termine auf der Seite anzeigen', 1833 'ashow.show_toggle_hint': '(Termine-Button in der Leiste + die Termin-Seite)', 1834 'ashow.save': 'Speichern', 1835 'ashow.off': 'aus', 1836 'ashow.off_notice_1': 'Die Termine stehen gerade auf', 1837 'ashow.off_notice_2': '— Besucher sehen keinen Termine-Button und die Termin-Seite ist nicht erreichbar. Schalte sie ein, um deine Veranstaltungen anzuzeigen.', 1838 'ashow.subscribers': 'Abonnent(en) für Veranstaltungs-Ankündigungen.', 1839 'ashow.smtp_warn': '⚠ SMTP nicht eingerichtet — Veranstaltungen werden gespeichert, aber Benachrichtigungs-Mails können erst versendet werden, sobald du SMTP einträgst.', 1840 'ashow.f_date': 'Datum', 1841 'ashow.f_time': 'Uhrzeit (optional)', 1842 'ashow.f_city': 'Ort', 1843 'ashow.f_country': 'Land (optional)', 1844 'ashow.f_venue': 'Location/Saal (optional)', 1845 'ashow.f_ticket': 'Ticket-URL (optional)', 1846 'ashow.f_notes': 'Notiz (optional)', 1847 'ashow.f_notes_ph': 'Support: ...', 1848 'ashow.notify_label': 'Abonnenten per E-Mail benachrichtigen', 1849 'ashow.smtp_required': '(SMTP erforderlich)', 1850 'ashow.add_event': '+ Veranstaltung hinzufügen', 1851 'ashow.del_confirm': 'Veranstaltung löschen??', 1852 'ashow.empty': 'Noch keine Veranstaltungen.', 1853 'anews.title': 'Newsletter', 1854 'anews.confirmed': 'bestätigt', 1855 'anews.pending': 'ausstehend', 1856 'anews.unsub': 'abgemeldet', 1857 'anews.smtp_warn_1': '⚠ SMTP ist noch nicht eingerichtet. Anmeldungen werden zwar gesammelt, aber der Versand ist erst möglich, sobald du deine SMTP-Daten einträgst', 1858 'anews.smtp_warn_2': 'in', 1859 'anews.share': 'Anmeldelink zum Teilen:', 1860 'anews.subject': 'Betreff', 1861 'anews.subject_ph': 'Neue Single draußen!', 1862 'anews.body': 'Nachricht', 1863 'anews.body_ph': 'Schreibe dein Update…', 1864 'anews.send_confirm': 'Newsletter an {n} bestätigte(n) Abonnent(en) senden?', 1865 'anews.send_btn': 'An {n} Abonnent(en) senden', 1866 'anews.sent_heading': 'Versendet', 1867 'anews.recipients': 'Empfänger', 1868 'aupd.title': 'Updates', 1869 'aupd.back_admin': 'Verwaltung', 1870 'aupd.version_heading': 'Version dieses Klonkt', 1871 'aupd.app_version': 'App-Version', 1872 'aupd.current': 'Aktuell', 1873 'aupd.current_unknown': 'unbekannt (noch nicht über den Update-Button aktualisiert)', 1874 'aupd.latest': 'Neueste', 1875 'aupd.latest_failed': 'die neueste Version konnte nicht abgerufen werden', 1876 'aupd.no_source': 'Keine Update-Quelle erreichbar', 1877 'aupd.uptodate': 'Aktuell', 1878 'aupd.update_available': 'Update verfügbar', 1879 'aupd.behind_one': '{n} Commit zurück', 1880 'aupd.behind_many': '{n} Commits zurück', 1881 'aupd.run_confirm': 'Die Seite wird auf die neueste Version gebracht und kurz neu gestartet. Fortfahren?', 1882 'aupd.redeploy': 'Erneut ausrollen', 1883 'aupd.update_now': 'Jetzt aktualisieren', 1884 'aupd.help': 'Das Aktualisieren holt den neuesten Code und startet diese Seite kurz neu (~10s). Lass dir Zeit — es geht nichts verloren (deine Beiträge, Einstellungen und dein Zirkel bleiben erhalten).', 1885 'aepk.title': 'Pressekit bearbeiten', 1886 'aepk.back_admin': 'Verwaltung', 1887 'aepk.view_epk': 'Pressekit ansehen', 1888 'aepk.text_heading': 'Text', 1889 'aepk.text_help': 'Das Pressekit (/pers) zeigt deinen Seitennamen + Foto, diese Bio und Kontakt sowie automatisch deine meistgehörten Titel und neuesten Beiträge. Lass die Bio leer, um den Seiten-Slogan zu verwenden; lass Kontakt leer, um nichts anzuzeigen (deine Login-E-Mail wird nie automatisch angezeigt).', 1890 'aepk.bio_label': 'Presse-Bio', 1891 'aepk.bio_ph': 'Kurze Beschreibung von dir/dem Projekt für Presse & Booker.', 1892 'aepk.contact_label': 'Presse-Kontakt', 1893 'aepk.contact_ph': 'z. B. presse@deinedomain.de oder ein Booking-Link', 1894 'aepk.tracks_label': 'Titel im Pressekit', 1895 'aepk.tracks_hint': '(wähle max. {n}; leer lassen für automatisch die Top {n} meistgehört)', 1896 'aepk.no_tracks': 'Noch keine Titel — füge zuerst Audio unter Verwaltung → Audio hinzu.', 1897 'aepk.untitled': '(ohne Titel)', 1898 'aepk.save': 'Speichern', 1899 'ahelp.back': 'Verwaltung', 1900 'ahelp.title': 'Anleitung', 1901 'ahelp.intro': 'Erklärung aller Funktionen. Tippe unten, um nach einem Thema oder einer Anleitung zu suchen.', 1902 'ahelp.search_placeholder': 'Suchen… (z. B. \'Termine\', \'Foto\', \'Zirkel\', \'Downloads\')', 1903 'ahelp.search_aria': 'In der Anleitung suchen', 1904 'ahelp.premium': 'Premium', 1905 'ahelp.empty': 'Keine Themen für deine Suche gefunden.', 1906 'ahelp.s_newpost_h': 'Einen neuen Beitrag schreiben', 1907 'ahelp.s_newpost_b': 'Verwaltung → <strong>Neuer Beitrag</strong>. Gib einen Titel ein und schreibe deinen Inhalt. Unten wählst du den Status: <em>Entwurf</em> (nicht sichtbar) oder <em>veröffentlicht</em>. Entwürfe stehen oben in deiner Verwaltungsübersicht, damit du sie wiederfindest.', 1908 'ahelp.s_excerpt_h': 'Zusammenfassung & Zirkel-Vorschau', 1909 'ahelp.s_excerpt_b': 'Das Feld <strong>Zusammenfassung & Zirkel-Vorschau</strong> (der Auszug) ist der kurze Vorschautext unter einem Beitrag in Listen sowie die Zusammenfassung, die andere Seiten anzeigen, wenn sie deinen Beitrag über einen <strong>Zirkel</strong> übernehmen. Lässt du es leer, wird automatisch der Anfang des Beitrags verwendet.', 1910 'ahelp.s_pin_h': 'Beitrag anheften / Reihenfolge', 1911 'ahelp.s_pin_b': 'Im Beitragseditor kannst du einen Beitrag mit einem Rang <strong>anheften</strong> (1 = ganz oben). Angeheftete Beiträge stehen in der Zeitleiste/im Raster ganz vorne, sortiert nach ihrem Rang. Rang leer oder 0 = nicht angeheftet.', 1912 'ahelp.s_schedule_h': 'Veröffentlichung planen & nur für Fans', 1913 'ahelp.s_schedule_b': 'Im Editor kannst du ein <strong>Veröffentlichungsdatum</strong> in der Zukunft festlegen — der Beitrag erscheint dann automatisch zu diesem Zeitpunkt. Mit <strong>Nur für Fans</strong> sehen nicht angemeldete Besucher nur einen Teaser + Login-Einladung; angemeldete Fans sehen alles.', 1914 'ahelp.s_images_h': 'Bilder in Beiträgen', 1915 'ahelp.s_images_b': 'Bilder im Text und das Titelbild werden immer <strong>vollständig</strong> in voller Breite angezeigt (nicht zugeschnitten), in ihrer natürlichen Höhe.', 1916 'ahelp.s_audio_h': 'Audio & Titel hinzufügen', 1917 'ahelp.s_audio_b': 'Verwaltung → <strong>Audio</strong>. Lade eine Datei hoch oder füge einen <em>Nur-Link</em>-Titel hinzu (ohne Upload, nur „Öffnen in“-Links). Pro Titel gibst du Titel, Künstler, Cover und optional Album/Position an. In einem Beitrag zeigst du einen Titel mit dem Shortcode <code>[[track:id]]</code>, ein Album mit <code>[[album:Name]]</code>, eine Playlist mit <code>[[playlist:id]]</code>.', 1918 'ahelp.s_credit_h': 'Credit, Lizenz & „Öffnen in“', 1919 'ahelp.s_credit_b': 'Pro Titel kannst du einen <strong>Eigentümer/Credit</strong> (mit ©-Schaltfläche) und eine <strong>Lizenz</strong> festlegen — diese werden auch in die mp3-Metadaten geschrieben. Mit den <strong>Öffnen-in</strong>-Feldern (Spotify / YouTube / SoundCloud) erscheinen Schaltflächen, um den Titel auf diesen Plattformen zu öffnen.', 1920 'ahelp.s_downloads_h': 'Downloads', 1921 'ahelp.s_downloads_b': 'Markiere einen Titel in Verwaltung → Audio als <strong>herunterladbar</strong> (⬇-Schaltfläche). Besucher finden sie auf der Seite <strong>/downloads</strong> und hinterlassen ihre E-Mail-Adresse, um die Datei zu erhalten (sie kommt auf deine Mailingliste). Möchtest du Downloads prominent im Feed? Erstelle einen normalen Beitrag mit dem Slug <code>downloads</code> und hefte ihn an.', 1922 'ahelp.s_albums_h': 'Alben & Playlists', 1923 'ahelp.s_albums_b': 'Gib Titeln dasselbe <strong>Album</strong> + eine <strong>Position</strong>, um ein Album zu bilden. Playlists erstellst du in Verwaltung → <strong>Playlists</strong>. Beide zeigst du in einem Beitrag mit <code>[[album:Name]]</code> oder <code>[[playlist:id]]</code>.', 1924 'ahelp.s_agenda_h': 'Termine / Veranstaltungen', 1925 'ahelp.s_agenda_b': 'Verwaltung → <strong>Termine</strong>. Aktiviere oben <strong>„Termine auf der Seite anzeigen“</strong> — dann erscheint die Termine-Schaltfläche in der Leiste und die Terminseite ist erreichbar. Füge Veranstaltungen hinzu (Datum, Ort, Veranstaltungsort, Tickets). Besucher können sich (unabhängig vom Newsletter) für eine Benachrichtigung bei einer neuen Veranstaltung anmelden.', 1926 'ahelp.s_presskit_h': 'Pressekit', 1927 'ahelp.s_presskit_b': 'Eine teilbare Presseseite unter <strong>/pers</strong>. Bearbeite sie über die Schaltfläche <strong>✎ Bearbeiten</strong> auf der Seite selbst (nur du siehst sie). Lege eine kurze Presse-Bio + Kontakt fest und wähle <strong>bis zu 5 Titel</strong>, die angezeigt werden (oder lass es leer = automatisch die 5 meistgehörten).', 1928 'ahelp.s_circles_h': 'Zirkel (Föderation)', 1929 'ahelp.s_circles_b': 'In Verwaltung → Einstellungen setzt du deine Seite in den <strong>Zirkel</strong>-Modus. Danach fügst du in Verwaltung → <strong>Zirkel</strong> andere Klonkt-Seiten hinzu; ihre öffentlichen Beiträge erscheinen dann in deinem <strong>/cirkel</strong>-Feed. Was andere von <em>dir</em> sehen, ist der Titel + die „Zusammenfassung & Zirkel-Vorschau“. Hubs nehmen nicht an Zirkeln teil (nur Solo-Seiten).', 1930 'ahelp.s_stats_h': 'Statistiken', 1931 'ahelp.s_stats_b': 'Verwaltung → <strong>Statistiken</strong>. Cookiefrei gemessen. <strong>Besuchertage</strong> = eindeutige Besucher pro Tag, aufsummiert (keine Personenzahl). <strong>Aufrufe</strong> = Start-/Feed- und Beitragsaufrufe. Verwaltungsbesuche und Bots zählen nicht mit. Gut für Trends; absolute Zahlen mit Vorsicht genießen.', 1932 'ahelp.s_newsletter_h': 'Newsletter', 1933 'ahelp.s_newsletter_b': 'Verwaltung → <strong>Newsletter</strong>: Verfasse eine Nachricht und sende sie an deine bestätigten Abonnenten. Besucher melden sich über die Fußzeile oder <strong>/nieuwsbrief</strong> an. Zum Versenden muss E-Mail (SMTP) eingerichtet sein.', 1934 'ahelp.s_linkbio_h': 'Link-in-Bio', 1935 'ahelp.s_linkbio_b': 'Eine Linktree-ähnliche Seite unter <strong>/links</strong> mit deinen Profil-Links. Die Klicks pro Link siehst du in den Statistiken.', 1936 'ahelp.s_embed_h': 'Einbettbarer Player', 1937 'ahelp.s_embed_b': 'Verwaltung → Audio zeigt einen kopierbaren <code><iframe></code>-Code (<strong>/embed</strong>), mit dem du deinen Player auf einer anderen Website einbetten kannst.', 1938 'ahelp.s_appearance_h': 'Erscheinungsbild (Theme, Foto, Akzent)', 1939 'ahelp.s_appearance_b': 'Verwaltung → <strong>Erscheinungsbild</strong>: Lege deinen Seitennamen, Slogan, dein Profilfoto, deine Akzentfarbe und Farbpalette fest sowie die Standard-Feed-Ansicht (Zeitleiste oder Raster).', 1940 'ahelp.s_tenancy_h': 'Solo- / Hub-Modus', 1941 'ahelp.s_tenancy_b': 'Verwaltung → <strong>Einstellungen</strong>: <em>Solo</em> = eine Seite (du). <em>Hub</em> = eine Firmen-/Label-Seite mit mehreren Kreativen, jeweils mit eigenem Klonkt unter <code>/user/<Name></code>. <em>Zirkel</em> = Solo + Föderation mit anderen Seiten.', 1942 'ahelp.s_premium_h': 'Premium / Patreon', 1943 'ahelp.s_premium_b': 'Premium-Funktionen (Hub, Statistiken, Termine, Downloads, Pressekit, Newsletter, Link-in-Bio, Embed, Fan-Login) schaltest du in Verwaltung → Einstellungen frei, indem du Patreon verknüpfst ($10 lebenslang). Updates und die Kern-App bleiben immer kostenlos.', 1944 'ahelp.s_google_h': 'Google-Login (Besucher)', 1945 'ahelp.s_google_b': 'Verwaltung → Einstellungen → <strong>Google</strong>. Optional: Lass Besucher/Fans sich mit Google anmelden, um zu kommentieren. Du verwaltest mit deinem Passwort — Google gewährt nie Verwaltungsrechte. Trage deine eigenen Google-Client-Daten ein (pro Seite).', 1946 'ahelp.s_updates_h': 'Updates', 1947 'ahelp.s_updates_b': 'Verwaltung → <strong>Updates</strong> (nur God) zeigt die aktuelle Version und ob eine neuere verfügbar ist. Mit „Jetzt aktualisieren“ holst du dir die neueste Version.', 298 1948 }, 299 1949 }; -
src/views/pages/admin-audio.ejs
r929d98d rbbf9d1a 4 4 <header class="ax-header"> 5 5 <div> 6 <h1> Audio tracks</h1>7 <p class="ax-tagline"> Site-level MP3s. Gebruik <code>[[track:<id>]]</code> in een post om een play-knop in te voegen.</p>6 <h1><%= t('aaud.title') %></h1> 7 <p class="ax-tagline"><%= t('aaud.tagline_pre') %> <code>[[track:<id>]]</code> <%= t('aaud.tagline_post') %></p> 8 8 </div> 9 9 </header> … … 14 14 <%# ── UPLOAD ──────────────────────────────────────────────── %> 15 15 <section class="ax-card"> 16 <div class="ax-card-title"> Upload</div>16 <div class="ax-card-title"><%= t('aaud.upload') %></div> 17 17 18 18 <%# Bulk drag-drop zone. Multiple files at once, transcoded sequentially. … … 25 25 <div class="ax-row ax-row-2"> 26 26 <label class="ax-field"> 27 <span> Artiest <small>(toegepast op alle bestanden)</small></span>28 <input type="text" id="batch-artist" placeholder=" Optioneel">27 <span><%= t('aaud.artist') %> <small><%= t('aaud.applied_all') %></small></span> 28 <input type="text" id="batch-artist" placeholder="<%= t('aaud.optional') %>"> 29 29 </label> 30 30 <label class="ax-field"> 31 <span> Album <small>(toegepast op alle bestanden)</small></span>32 <input type="text" id="batch-album" placeholder=" Optioneel">31 <span><%= t('aaud.album') %> <small><%= t('aaud.applied_all') %></small></span> 32 <input type="text" id="batch-album" placeholder="<%= t('aaud.optional') %>"> 33 33 </label> 34 34 </div> 35 35 <label class="ax-field ax-file"> 36 <span> Cover <small>(optioneel, toegepast op alle bestanden — jpg/png/webp/gif, max 5 MB)</small></span>36 <span><%= t('aaud.cover') %> <small><%= t('aaud.cover_hint') %></small></span> 37 37 <span class="ax-file-control"> 38 <span class="ax-btn ax-btn-secondary ax-file-btn">🖼 Kies cover</span>39 <span class="ax-file-name" data-empty> Geen bestand gekozen</span>38 <span class="ax-btn ax-btn-secondary ax-file-btn">🖼 <%= t('aaud.choose_cover') %></span> 39 <span class="ax-file-name" data-empty><%= t('aaud.no_file') %></span> 40 40 <input type="file" id="batch-cover" accept="image/jpeg,image/png,image/webp,image/gif"> 41 41 </span> … … 45 45 <label class="ax-dropzone" id="audio-dropzone" tabindex="0"> 46 46 <span class="ax-dropzone-icon">🎵</span> 47 <strong class="ax-dropzone-title"> Sleep audio hierheen</strong>48 <span class="ax-dropzone-sub"> of klik om bestanden te kiezen<br><small>mp3, m4a, ogg, opus, flac · max <%= maxBytesMb %> MB · wav max <%= maxWavMb %> MB</small></span>47 <strong class="ax-dropzone-title"><%= t('aaud.drag_here') %></strong> 48 <span class="ax-dropzone-sub"><%= t('aaud.or_click') %><br><small>mp3, m4a, ogg, opus, flac · max <%= maxBytesMb %> MB · wav max <%= maxWavMb %> MB</small></span> 49 49 <input type="file" id="audio-files" accept="audio/*" multiple> 50 50 </label> … … 54 54 55 55 <div class="ax-form-actions" id="upload-actions" hidden> 56 <button type="button" class="ax-btn ax-btn-primary" id="start-upload-btn">⬆ Start upload</button>57 <button type="button" class="ax-btn" id="clear-queue-btn"> Wis lijst</button>56 <button type="button" class="ax-btn ax-btn-primary" id="start-upload-btn">⬆ <%= t('aaud.start_upload') %></button> 57 <button type="button" class="ax-btn" id="clear-queue-btn"><%= t('aaud.clear_list') %></button> 58 58 </div> 59 59 </div> … … 63 63 <section class="ax-card"> 64 64 <div class="ax-card-title" style="display:flex;align-items:center;gap:.5rem"> 65 Tracks<span class="ax-count"><%= tracks.length %></span>65 <%= t('aaud.tracks') %> <span class="ax-count"><%= tracks.length %></span> 66 66 <button type="button" class="ax-btn ax-btn-secondary" id="add-link-track-btn" 67 style="margin-left:auto;font-size:.85rem" title=" Een track zonder audiobestand — alleen titel + open-in links">68 + Track zonder audio67 style="margin-left:auto;font-size:.85rem" title="<%= t('aaud.add_link_track_title') %>"> 68 + <%= t('aaud.add_link_track') %> 69 69 </button> 70 70 </div> … … 73 73 <div class="ax-empty"> 74 74 <div class="ax-empty-icon">♫</div> 75 <p> Nog geen tracks. Upload er een hierboven.</p>75 <p><%= t('aaud.no_tracks') %></p> 76 76 </div> 77 77 <% } else { %> 78 <% 79 // Pre-fetch loop labels — `t` is shadowed by the loop var below. 80 var _aaudUntitled = t('aaud.untitled'); 81 var _aaudPlay = t('aaud.play'); 82 var _aaudEdit = t('aaud.edit'); 83 var _aaudDlOn = t('aaud.dl_on'); 84 var _aaudDlOff = t('aaud.dl_off'); 85 var _aaudDelete = t('aaud.delete'); 86 var _aaudCopy = t('aaud.copy_click'); 87 var _aaudDelConfirm = t('aaud.delete_confirm'); 88 %> 78 89 <ul class="ax-list"> 79 90 <% tracks.forEach(function(t) { %> … … 86 97 87 98 <div class="ax-track-meta"> 88 <div class="ax-track-title" data-cell="title"><%= t.title || '(zonder titel)'%></div>99 <div class="ax-track-title" data-cell="title"><%= t.title || _aaudUntitled %></div> 89 100 <div class="ax-track-sub"> 90 101 <span data-cell="artist"><%= t.artist || '—' %></span> … … 92 103 <span class="ax-track-sep">·</span><span><%= t.size ? Math.round(t.size/1024) + ' KB' : '—' %></span> 93 104 </div> 94 <code class="ax-embed" data-copy="[[track:<%= t.id %>]]" title=" Klik om te kopiëren">[[track:<%= t.id %>]]</code>105 <code class="ax-embed" data-copy="[[track:<%= t.id %>]]" title="<%= _aaudCopy %>">[[track:<%= t.id %>]]</code> 95 106 </div> 96 107 … … 100 111 data-stream-url="<%= t.stream_url %>" 101 112 data-track-id="<%= t.id %>" 102 aria-label=" Afspelen" title="Afspelen">113 aria-label="<%= _aaudPlay %>" title="<%= _aaudPlay %>"> 103 114 <span class="ax-track-play-icon" aria-hidden="true">▶</span> 104 115 </button> 105 116 <% } %> 106 <button type="button" class="ax-icon-btn" data-track-edit data-id="<%= t.id %>" aria-label=" Bewerken" title="Bewerken">✎</button>117 <button type="button" class="ax-icon-btn" data-track-edit data-id="<%= t.id %>" aria-label="<%= _aaudEdit %>" title="<%= _aaudEdit %>">✎</button> 107 118 <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %> 108 119 <button type="button" class="ax-icon-btn" data-track-dl data-id="<%= t.id %>" data-on="<%= t.downloadable ? '1' : '0' %>" 109 aria-label=" Download-voor-email" title="<%= t.downloadable ? 'Download-voor-email staat AAN — klik om uit te zetten' : 'Download-voor-email staat uit — klik om aan te zetten'%>"120 aria-label="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>" title="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>" 110 121 style="<%= t.downloadable ? 'color:var(--accent,#6b8f71)' : 'opacity:.5' %>">⬇</button> 111 122 <% } %> 112 <form action="/admin/audio/<%= t.id %>/delete" method="post" onsubmit="return confirm(' Track verwijderen?')" class="ax-track-delete">113 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label=" Verwijderen" title="Verwijderen">🗑</button>123 <form action="/admin/audio/<%= t.id %>/delete" method="post" onsubmit="return confirm('<%= _aaudDelConfirm %>')" class="ax-track-delete"> 124 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _aaudDelete %>" title="<%= _aaudDelete %>">🗑</button> 114 125 </form> 115 126 </div> … … 122 133 <% if ((typeof premiumUnlocked === 'undefined' || premiumUnlocked) && typeof embedUrl !== 'undefined') { %> 123 134 <section class="ax-section" style="margin-top:1.5rem"> 124 <h2 style="margin:0 0 .4rem">🔊 Embedbare speler</h2>125 <p style="opacity:.75;font-size:.9rem;margin:0 0 .6rem"> Plak deze code op je eigen website/blog om je muziek met deze speler in te sluiten:</p>135 <h2 style="margin:0 0 .4rem">🔊 <%= t('aaud.embed_player') %></h2> 136 <p style="opacity:.75;font-size:.9rem;margin:0 0 .6rem"><%= t('aaud.embed_hint') %></p> 126 137 <textarea readonly rows="3" onclick="this.select()" style="width:100%;font-family:monospace;font-size:12px;padding:10px;border-radius:8px;border:1px solid var(--rule);background:var(--paper-2);color:inherit;"><iframe src="<%= embedUrl %>" width="100%" height="420" frameborder="0" loading="lazy" style="border-radius:12px"></iframe></textarea> 127 <p style="margin:.6rem 0 0"><a class="btn" href="<%= embedUrl %>" target="_blank">▶ Speler-voorbeeld openen</a></p>138 <p style="margin:.6rem 0 0"><a class="btn" href="<%= embedUrl %>" target="_blank">▶ <%= t('aaud.preview_player') %></a></p> 128 139 </section> 129 140 <% } %> … … 573 584 nameEl.removeAttribute('data-empty'); 574 585 } else { 575 nameEl.textContent = ' Geen bestand gekozen';586 nameEl.textContent = '<%= t('aaud.no_file') %>'; 576 587 nameEl.setAttribute('data-empty', ''); 577 588 } … … 592 603 if (icon) icon.textContent = playing ? '⏸' : '▶'; 593 604 btn.classList.toggle('is-playing', playing); 594 btn.setAttribute('aria-label', playing ? ' Pauzeren' : 'Afspelen');605 btn.setAttribute('aria-label', playing ? '<%= t('aaud.pause') %>' : '<%= t('aaud.play') %>'); 595 606 } 596 607 … … 695 706 function setItemStatus(item, status, label) { 696 707 const labels = { 697 queued: ' Wachten',698 uploading: ' Uploaden…',699 transcoding: ' Converteren…',700 done: '✓ Klaar',701 error: '✗ Fout',708 queued: '<%= t('aaud.st_queued') %>', 709 uploading: '<%= t('aaud.st_uploading') %>', 710 transcoding: '<%= t('aaud.st_transcoding') %>', 711 done: '✓ <%= t('aaud.st_done') %>', 712 error: '✗ <%= t('aaud.st_error') %>', 702 713 }; 703 714 item.status = status; … … 726 737 li.innerHTML = 727 738 '<div class="ax-queue-name"></div>' + 728 '<span class="ax-queue-status ax-queue-status--queued"> Wachten</span>';739 '<span class="ax-queue-status ax-queue-status--queued"><%= t('aaud.st_queued') %></span>'; 729 740 // Use textContent to avoid HTML-injection if a filename contains markup. 730 741 li.querySelector('.ax-queue-name').textContent = file.name; … … 813 824 } catch (err) { 814 825 console.error('upload failed for', item.file.name, err); 815 setItemStatus(item, 'error', '✗ ' + (err.message || ' Mislukt'));826 setItemStatus(item, 'error', '✗ ' + (err.message || '<%= t('aaud.failed') %>')); 816 827 } 817 828 } … … 864 875 let data; 865 876 try { data = await res.json(); } 866 catch (_) { throw new Error(' Onverwacht serverantwoord(' + res.status + ')'); }877 catch (_) { throw new Error('<%= t('aaud.err_unexpected') %> (' + res.status + ')'); } 867 878 868 879 if (!res.ok || !data.ok) { … … 870 881 } 871 882 872 setItemStatus(item, 'done', '✓ ' + (data.title || ' Klaar'));883 setItemStatus(item, 'done', '✓ ' + (data.title || '<%= t('aaud.st_done') %>')); 873 884 } catch (err) { 874 885 clearTimeout(transcodeHint); … … 885 896 el.classList.add('is-copied'); 886 897 const original = el.textContent; 887 el.textContent = '✓ gekopieerd';898 el.textContent = '✓ <%= t('aaud.copied') %>'; 888 899 setTimeout(() => { 889 900 el.classList.remove('is-copied'); … … 903 914 method: 'POST', credentials: 'same-origin', 904 915 headers: { 'Content-Type': 'application/json' }, 905 body: JSON.stringify({ title: ' Nieuwe track' }),916 body: JSON.stringify({ title: '<%= t('aaud.new_track') %>' }), 906 917 }); 907 918 const j = await r.json(); 908 if (!r.ok || !j.ok) throw new Error(j.error || ' Aanmaken mislukt');919 if (!r.ok || !j.ok) throw new Error(j.error || '<%= t('aaud.create_failed') %>'); 909 920 if (typeof window.openTrackEditor !== 'function') { location.reload(); return; } 910 921 window.openTrackEditor({ id: j.id, onSaved: () => location.reload() }); 911 922 } catch (err) { 912 alert(' Track aanmaken mislukt: ' + err.message);923 alert('<%= t('aaud.create_failed') %>: ' + err.message); 913 924 } finally { 914 925 addLinkBtn.disabled = false; … … 923 934 btn.addEventListener('click', () => { 924 935 if (typeof window.openTrackEditor !== 'function') { 925 alert(' Track editor niet geladen');936 alert('<%= t('aaud.editor_not_loaded') %>'); 926 937 return; 927 938 } … … 936 947 const artistEl = row.querySelector('[data-cell="artist"]'); 937 948 const albumEl = row.querySelector('[data-cell="album"]'); 938 if (titleEl) titleEl.textContent = track.title || ' (zonder titel)';949 if (titleEl) titleEl.textContent = track.title || '<%= t('aaud.untitled') %>'; 939 950 if (artistEl) artistEl.textContent = track.artist || '—'; 940 951 if (albumEl) { … … 984 995 btn.style.opacity = want ? '1' : '.5'; 985 996 btn.title = want 986 ? ' Download-voor-email staat AAN — klik om uit te zetten'987 : ' Download-voor-email staat uit — klik om aan te zetten';997 ? '<%= t('aaud.dl_on') %>' 998 : '<%= t('aaud.dl_off') %>'; 988 999 } catch (e) { 989 alert(' Kon niet wijzigen: ' + (e.message || e));1000 alert('<%= t('aaud.change_failed') %>: ' + (e.message || e)); 990 1001 } finally { 991 1002 btn.disabled = false; -
src/views/pages/admin-circle.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-page"> 2 <h1> Cirkel</h1>3 <p><a href="/admin/settings" class="btn">← Instellingen</a></p>2 <h1><%= t('acir.title') %></h1> 3 <p><a href="/admin/settings" class="btn">← <%= t('acir.back_settings') %></a></p> 4 4 5 5 <% if (success) { %><div class="alert alert-success"><%= success %></div><% } %> … … 8 8 <% if (tenancy !== 'circle') { %> 9 9 <section class="set-card"> 10 <p class="set-help"> De modus staat niet op <strong>Cirkels</strong>. Zet 'm aan bij11 <a href="/admin/settings"> Instellingen</a> om je cirkel-feed te tonen op<code>/cirkel</code>.12 Je kunt hieronder al wel bronnen klaarzetten.</p>10 <p class="set-help"><%= t('acir.mode_off_1') %> <strong><%= t('acir.circles') %></strong>. <%= t('acir.mode_off_2') %> 11 <a href="/admin/settings"><%= t('acir.settings') %></a> <%= t('acir.mode_off_3') %> <code>/cirkel</code>. 12 <%= t('acir.mode_off_4') %></p> 13 13 </section> 14 14 <% } %> 15 15 16 16 <section class="set-card" style="margin-top:1rem"> 17 <h2>Mijn zichtbaarheid</h2> 18 <p class="set-help">Doe je mee aan cirkels? Dit maakt je <strong>al-publieke</strong> posts 19 ophaalbaar voor andere Klonkt-sites via een ondertekende feed 20 (<code>/.klonkt/outbox.json</code>). Het is een deelname-keuze, geen privacy-slot: 21 je posts blijven sowieso openbaar op je site, ook als dit uit staat. Wil je iets 22 afschermen, maak die post dan niet-publiek.</p> 17 <h2><%= t('acir.visibility_title') %></h2> 18 <p class="set-help"><%= t('acir.visibility_help_1') %> <strong><%= t('acir.all_public') %></strong> <%= t('acir.visibility_help_2') %> 19 (<code>/.klonkt/outbox.json</code>). <%= t('acir.visibility_help_3') %></p> 23 20 <form method="post" action="/admin/circle/allow" class="set-form"> 24 21 <label class="set-opt" style="align-items:center"> 25 22 <input type="checkbox" name="allow_circle" value="on" <%= allowCircle ? 'checked' : '' %>> 26 <span><strong> Toon mijn site in cirkels van anderen</strong></span>23 <span><strong><%= t('acir.show_in_circles') %></strong></span> 27 24 </label> 28 <button type="submit" class="btn btn-primary"> Opslaan</button>25 <button type="submit" class="btn btn-primary"><%= t('acir.save') %></button> 29 26 </form> 30 27 </section> 31 28 32 29 <section class="set-card" style="margin-top:1rem"> 33 <h2>Klonkt-site toevoegen</h2> 34 <p class="set-help">Plak de basis-URL van een andere Klonkt-site. Asymmetrisch: jij toont 35 hen, los van of zij jou tonen.</p> 30 <h2><%= t('acir.add_title') %></h2> 31 <p class="set-help"><%= t('acir.add_help') %></p> 36 32 <form method="post" action="/admin/circle/add" class="set-form"> 37 33 <label class="set-field"> 38 <span> URL</span>34 <span><%= t('acir.url') %></span> 39 35 <input type="url" name="remote_url" placeholder="https://artiest.klonkt.com" required> 40 36 </label> 41 37 <label class="set-field"> 42 <span> Label <small style="font-weight:400">— optioneel</small></span>43 <input type="text" name="label" maxlength="80" placeholder=" bijv. Joost Klein">38 <span><%= t('acir.label') %> <small style="font-weight:400">— <%= t('acir.optional') %></small></span> 39 <input type="text" name="label" maxlength="80" placeholder="<%= t('acir.label_ph') %>"> 44 40 </label> 45 <button type="submit" class="btn btn-primary"> Toevoegen</button>41 <button type="submit" class="btn btn-primary"><%= t('acir.add') %></button> 46 42 </form> 47 43 </section> 48 44 49 45 <section class="set-card" style="margin-top:1rem"> 50 <h2> In mijn cirkel (<%= links.length %>)</h2>46 <h2><%= t('acir.in_circle', { n: links.length }) %></h2> 51 47 <% if (!links.length) { %> 52 <p class="set-help"> Nog geen bronnen. Voeg er hierboven een toe.</p>48 <p class="set-help"><%= t('acir.no_sources') %></p> 53 49 <% } else { %> 54 50 <form method="post" action="/admin/circle/sync-all" style="margin:0 0 .9rem"> 55 <button type="submit" class="btn">↻ Alles nu synchroniseren</button>51 <button type="submit" class="btn">↻ <%= t('acir.sync_all') %></button> 56 52 </form> 53 <% 54 var _cActive = t('acir.st_active'); 55 var _cPosts = t('acir.posts'); 56 var _cLast = t('acir.last'); 57 var _cMismatch = t('acir.st_mismatch'); 58 var _cMismatchReason = t('acir.mismatch_reason'); 59 var _cError = t('acir.st_error'); 60 var _cPaused = t('acir.st_paused'); 61 var _cRefresh = t('acir.refresh'); 62 var _cRemove = t('acir.remove'); 63 var _cRemoveConfirm = t('acir.remove_confirm'); 64 %> 57 65 <ul class="circ-list"> 58 66 <% links.forEach(function(l){ %> … … 63 71 <div class="circ-status"> 64 72 <% if (l.status === 'active') { %> 65 <span class="circ-badge ok">✓ actief</span>66 <span class="circ-meta"><%= counts[l.id] || 0 %> posts<% if (l.last_synced) { %> · laatst: <%= l.last_synced %><% } %></span>73 <span class="circ-badge ok">✓ <%= _cActive %></span> 74 <span class="circ-meta"><%= counts[l.id] || 0 %> <%= _cPosts %><% if (l.last_synced) { %> · <%= _cLast %>: <%= l.last_synced %><% } %></span> 67 75 <% } else if (l.status === 'outdated') { %> 68 <span class="circ-badge err">↻ versie-mismatch</span>69 <div class="circ-reason"><%= l.last_error || 'Deze site draait een andere Klonkt-protocolversie — bijwerken nodig om te federeren.'%></div>76 <span class="circ-badge err">↻ <%= _cMismatch %></span> 77 <div class="circ-reason"><%= l.last_error || _cMismatchReason %></div> 70 78 <% } else if (l.status === 'error') { %> 71 <span class="circ-badge err">⚠ fout</span>79 <span class="circ-badge err">⚠ <%= _cError %></span> 72 80 <% if (l.last_error) { %><div class="circ-reason"><%= l.last_error %></div><% } %> 73 81 <% } else if (l.status === 'paused') { %> 74 <span class="circ-badge">⏸ gepauzeerd</span>82 <span class="circ-badge">⏸ <%= _cPaused %></span> 75 83 <% } else { %> 76 84 <%# Onbekende status: toon 'm rauw + eventuele reden i.p.v. stil "gepauzeerd". %> … … 82 90 <div class="circ-actions"> 83 91 <form method="post" action="/admin/circle/<%= l.id %>/sync" style="display:inline"> 84 <button type="submit" class="btn"> Verversen</button>92 <button type="submit" class="btn"><%= _cRefresh %></button> 85 93 </form> 86 94 <form method="post" action="/admin/circle/<%= l.id %>/remove" style="display:inline" 87 onsubmit="return confirm(' Verwijderen uit je cirkel?')">88 <button type="submit" class="btn btn-danger"> Verwijder</button>95 onsubmit="return confirm('<%= _cRemoveConfirm %>')"> 96 <button type="submit" class="btn btn-danger"><%= _cRemove %></button> 89 97 </form> 90 98 </div> -
src/views/pages/admin-comments.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-comments-page"> 2 <p><a href="/admin" class="btn">← Beheer</a></p>3 <h1> Comment moderation</h1>2 <p><a href="/admin" class="btn">← <%= t('acom.back') %></a></p> 3 <h1><%= t('acom.title') %></h1> 4 4 5 5 <p class="muted"> 6 Mode for this site:<strong><%= moderationMode %></strong>6 <%= t('acom.mode_for_site') %> <strong><%= moderationMode %></strong> 7 7 <% if (moderationMode === 'trust') { %> 8 — comments are auto-approved. Switch to <em>moderate</em> in9 <a href="/admin/sites/<%= site.slug %>/edit"> site settings</a> to queue them.8 — <%= t('acom.mode_trust_a') %> <em><%= t('acom.mode_moderate_word') %></em> 9 <a href="/admin/sites/<%= site.slug %>/edit"><%= t('acom.site_settings') %></a> <%= t('acom.mode_trust_b') %> 10 10 <% } else { %> 11 — new comments require approval before showing up on posts.11 — <%= t('acom.mode_moderate_hint') %> 12 12 <% } %> 13 13 </p> … … 16 16 <% if (error) { %><div class="audio-flash audio-flash--err"><%= error %></div><% } %> 17 17 18 <h2> Pending (<%= pending.length %>)</h2>18 <h2><%= t('acom.pending', { n: pending.length }) %></h2> 19 19 <% if (!pending.length) { %> 20 <p class="muted"> Nothing waiting.</p>20 <p class="muted"><%= t('acom.nothing_waiting') %></p> 21 21 <% } else { %> 22 <% 23 var _c_reply = t('acom.reply'); 24 var _c_on = t('acom.on'); 25 var _c_approve = t('acom.approve'); 26 var _c_reject = t('acom.reject'); 27 %> 22 28 <ol class="moderation-list"> 23 29 <% pending.forEach(function(c) { %> … … 25 31 <div class="moderation-meta"> 26 32 <strong><%= c.author_username %></strong> 27 <% if (c.parent_comment_id) { %><span class="muted">( reply)</span><% } %>28 on<a href="<%= siteUrlBase %>/<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a>33 <% if (c.parent_comment_id) { %><span class="muted">(<%= _c_reply %>)</span><% } %> 34 <%= _c_on %> <a href="<%= siteUrlBase %>/<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a> 29 35 <span class="muted">· <%= formatDateTime(c.created_at) %></span> 30 36 </div> … … 32 38 <div class="moderation-actions"> 33 39 <form method="post" action="<%= siteUrlBase %>/admin/comments/<%= c.id %>/approve" style="display:inline"> 34 <button type="submit" class="btn btn-primary"> Approve</button>40 <button type="submit" class="btn btn-primary"><%= _c_approve %></button> 35 41 </form> 36 42 <form method="post" action="<%= siteUrlBase %>/admin/comments/<%= c.id %>/reject" style="display:inline"> 37 <button type="submit" class="btn btn-danger"> Reject</button>43 <button type="submit" class="btn btn-danger"><%= _c_reject %></button> 38 44 </form> 39 45 </div> … … 43 49 <% } %> 44 50 45 <h2> Recent decisions</h2>51 <h2><%= t('acom.recent') %></h2> 46 52 <% if (!recent.length) { %> 47 <p class="muted"> Nothing yet.</p>53 <p class="muted"><%= t('acom.nothing_yet') %></p> 48 54 <% } else { %> 49 55 <ul class="decision-list"> -
src/views/pages/admin-epk.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-page"> 2 <h1> Perskit bewerken</h1>3 <p><a href="/admin" class="btn">← Beheer</a> <a href="/pers" class="btn" target="_blank" rel="noopener">📰 Bekijk perskit</a></p>2 <h1><%= t('aepk.title') %></h1> 3 <p><a href="/admin" class="btn">← <%= t('aepk.back_admin') %></a> <a href="/pers" class="btn" target="_blank" rel="noopener">📰 <%= t('aepk.view_epk') %></a></p> 4 4 5 5 <% if (success) { %><div class="alert alert-success"><%= success %></div><% } %> 6 6 7 7 <section class="set-card"> 8 <h2> Tekst</h2>8 <h2><%= t('aepk.text_heading') %></h2> 9 9 <p class="set-help"> 10 De perskit (<code>/pers</code>) toont je sitenaam + foto, deze bio en contact, 11 plus automatisch je meest beluisterde nummers en recente posts. Laat de bio 12 leeg om de site-tagline te gebruiken; laat contact leeg om niets te tonen 13 (je login-mail wordt nooit automatisch getoond). 10 <%= t('aepk.text_help') %> 14 11 </p> 15 12 16 13 <form method="post" action="/admin/epk" class="set-form"> 17 14 <label class="set-field"> 18 <span> Pers-bio</span>15 <span><%= t('aepk.bio_label') %></span> 19 16 <textarea name="epk_bio" rows="5" maxlength="1000" 20 placeholder=" Korte beschrijving van jou/het project voor pers & boekers."><%= epkBio %></textarea>17 placeholder="<%= t('aepk.bio_ph') %>"><%= epkBio %></textarea> 21 18 </label> 22 19 <label class="set-field"> 23 <span> Pers-contact</span>20 <span><%= t('aepk.contact_label') %></span> 24 21 <input type="text" name="epk_contact" maxlength="300" 25 placeholder=" bv. pers@jouwdomein.nl of een boekingslink"22 placeholder="<%= t('aepk.contact_ph') %>" 26 23 value="<%= epkContact %>"> 27 24 </label> 28 25 29 26 <div class="set-field"> 30 <span> Nummers op de perskit <small>(kies er max <%= maxEpkTracks %>; laat leeg voor automatisch de top <%= maxEpkTracks %> meest beluisterd)</small></span>27 <span><%= t('aepk.tracks_label') %> <small><%= t('aepk.tracks_hint', { n: maxEpkTracks }) %></small></span> 31 28 <% if (!allTracks.length) { %> 32 <p class="set-help" style="margin:0"> Nog geen nummers — voeg eerst audio toe in Beheer → Audio.</p>29 <p class="set-help" style="margin:0"><%= t('aepk.no_tracks') %></p> 33 30 <% } else { %> 31 <% var _untitled = t('aepk.untitled'); %> 34 32 <div class="epk-track-pick" data-max="<%= maxEpkTracks %>"> 35 33 <% allTracks.forEach(function(t){ %> … … 37 35 <input type="checkbox" name="epk_tracks" value="<%= t.id %>" <%= chosenTracks.indexOf(t.id) >= 0 ? 'checked' : '' %>> 38 36 <span class="epk-box" aria-hidden="true">✓</span> 39 <span class="epk-pick-title"><%= t.title || '(zonder titel)'%></span>37 <span class="epk-pick-title"><%= t.title || _untitled %></span> 40 38 <% if (t.artist) { %><span class="epk-pick-artist"><%= t.artist %></span><% } %> 41 39 </label> … … 45 43 </div> 46 44 47 <button type="submit" class="btn btn-primary"> Opslaan</button>45 <button type="submit" class="btn btn-primary"><%= t('aepk.save') %></button> 48 46 </form> 49 47 </section> -
src/views/pages/admin-google.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-page"> 2 <h1> Google-login</h1>3 <p><a href="/admin/settings" class="btn">← Instellingen</a></p>2 <h1><%= t('agoog.title') %></h1> 3 <p><a href="/admin/settings" class="btn">← <%= t('agoog.back') %></a></p> 4 4 5 5 <% if (success) { %><div class="alert alert-success"><%= success %></div><% } %> … … 7 7 8 8 <section class="set-card"> 9 <h2> Google-login (bezoekers)</h2>10 <p class="set-help"> Laat bezoekers met Google inloggen om te reageren — met je <strong>eigen</strong>11 Google Cloud-client. Maak er een aan op9 <h2><%= t('agoog.card_title') %></h2> 10 <p class="set-help"><%= t('agoog.card_help_pre') %> <strong><%= t('agoog.card_help_own') %></strong> 11 <%= t('agoog.card_help_mid') %> 12 12 <a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener">console.cloud.google.com</a> 13 (OAuth client-ID, type "Web application").14 <% if (google && google.configured) { %><strong> Status: ingesteld ✓</strong><% } else { %>Nog niet ingesteld.<% } %></p>13 <%= t('agoog.card_help_post') %> 14 <% if (google && google.configured) { %><strong><%= t('agoog.status_set') %></strong><% } else { %><%= t('agoog.status_unset') %><% } %></p> 15 15 16 16 <details class="set-google-guide"> 17 <summary> Hoe stel ik dit in? (stap voor stap)</summary>17 <summary><%= t('agoog.guide_summary') %></summary> 18 18 <ol class="set-google-steps"> 19 <li> Ga naar de <a href="https://console.cloud.google.com/" target="_blank" rel="noopener">Google Cloud Console</a> en maak (of kies) een project.</li>20 <li><strong>APIs & Services → OAuth consent screen</strong>: User type <strong>External</strong>. Voeg de scopes <code>email</code>, <code>profile</code> en <code>openid</code> toe. (In testmodus: voeg jezelf toe als testgebruiker.)</li>21 <li><strong>APIs & Services → Credentials → Create credentials → OAuth client ID</strong>, type<strong>Web application</strong>.</li>22 <li> Bij <strong>Authorized redirect URIs</strong>: plak de redirect-URI hieronder (moet <strong>exact</strong> matchen).</li>23 <li> Kopieer de <strong>Client ID</strong> + <strong>Client Secret</strong> die je krijgt naar de velden hieronder en klik Opslaan.</li>19 <li><%= t('agoog.step1_pre') %> <a href="https://console.cloud.google.com/" target="_blank" rel="noopener">Google Cloud Console</a> <%= t('agoog.step1_post') %></li> 20 <li><strong>APIs & Services → OAuth consent screen</strong>: <%= t('agoog.step2_usertype') %> <strong>External</strong>. <%= t('agoog.step2_scopes_pre') %> <code>email</code>, <code>profile</code> <%= t('agoog.step2_scopes_and') %> <code>openid</code> <%= t('agoog.step2_scopes_post') %> <%= t('agoog.step2_testmode') %></li> 21 <li><strong>APIs & Services → Credentials → Create credentials → OAuth client ID</strong>, <%= t('agoog.step3_type') %> <strong>Web application</strong>.</li> 22 <li><%= t('agoog.step4_pre') %> <strong>Authorized redirect URIs</strong><%= t('agoog.step4_post') %></li> 23 <li><%= t('agoog.step5_pre') %> <strong>Client ID</strong> + <strong>Client Secret</strong> <%= t('agoog.step5_post') %></li> 24 24 </ol> 25 <p class="set-help" style="margin:.5rem 0 0"><strong> Let op:</strong> je hoeft <strong>geen API's aan te zetten</strong> — "Google Cloud APIs", People API e.d. zijn <em>niet</em> nodig. Inloggen werkt via OpenID Connect (alleen het toestemmingsscherm + de OAuth-client).</p>25 <p class="set-help" style="margin:.5rem 0 0"><strong><%= t('agoog.note_label') %></strong> <%= t('agoog.note_pre') %> <strong><%= t('agoog.note_noapi') %></strong> <%= t('agoog.note_mid') %> <em><%= t('agoog.note_not') %></em> <%= t('agoog.note_post') %></p> 26 26 </details> 27 27 28 28 <% if (google && google.redirectUri) { %> 29 29 <div class="set-redirect"> 30 <div class="set-redirect-label"> Redirect-URI — kopieer deze en plak 'm in Google</div>30 <div class="set-redirect-label"><%= t('agoog.redirect_label') %></div> 31 31 <code class="set-redirect-url" style="user-select:all"><%= google.redirectUri %></code> 32 32 <ol class="set-redirect-steps"> 33 <li> Ga naar<a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener">Google Cloud Console → APIs & Services → Credentials</a>.</li>34 <li> Open jouw <strong>OAuth client-ID</strong> (type "Web application").</li>35 <li> Onder <strong>Authorized redirect URIs</strong> → klik <strong>+ ADD URI</strong> en plak de regel hierboven.</li>36 <li> Klik <strong>Save</strong> en wacht ~1 minuut.</li>33 <li><%= t('agoog.rstep1_pre') %> <a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener">Google Cloud Console → APIs & Services → Credentials</a>.</li> 34 <li><%= t('agoog.rstep2') %></li> 35 <li><%= t('agoog.rstep3_pre') %> <strong>Authorized redirect URIs</strong> → <%= t('agoog.rstep3_mid') %> <strong>+ ADD URI</strong> <%= t('agoog.rstep3_post') %></li> 36 <li><%= t('agoog.rstep4_pre') %> <strong>Save</strong> <%= t('agoog.rstep4_post') %></li> 37 37 </ol> 38 <p class="set-redirect-warn"> Moet <strong>exact</strong> kloppen: <code>https://</code> (geen <code>http</code>), juiste domein, en <strong>geen</strong> schuine streep aan het eind. Anders krijg je <em>"Error 400: redirect_uri_mismatch"</em>.</p>38 <p class="set-redirect-warn"><%= t('agoog.warn_pre') %> <strong><%= t('agoog.warn_exact') %></strong> <%= t('agoog.warn_mid1') %> <code>https://</code> (<%= t('agoog.warn_nohttp') %> <code>http</code>)<%= t('agoog.warn_mid2') %> <strong><%= t('agoog.warn_noslash') %></strong> <%= t('agoog.warn_mid3') %> <em><%= t('agoog.warn_error') %></em>.</p> 39 39 </div> 40 40 <% } else { %> 41 <p class="set-help" style="color:var(--accent)"> Stel eerst <code>PUBLIC_BASE_URL</code> in — daar wordt de redirect-URI van afgeleid.</p>41 <p class="set-help" style="color:var(--accent)"><%= t('agoog.set_baseurl_pre') %> <code>PUBLIC_BASE_URL</code> <%= t('agoog.set_baseurl_post') %></p> 42 42 <% } %> 43 43 44 44 <form method="post" action="/admin/settings/google" class="set-form"> 45 45 <label class="set-field"> 46 <span> Client ID</span>46 <span><%= t('agoog.client_id') %></span> 47 47 <input type="text" name="google_client_id" value="<%= google ? google.clientId : '' %>" placeholder="123-abc.apps.googleusercontent.com" autocomplete="off"> 48 48 </label> 49 49 <label class="set-field"> 50 <span> Client Secret <% if (google && google.secretSet) { %><small style="font-weight:400">— ingesteld; leeg laten = ongewijzigd</small><% } %></span>51 <input type="password" name="google_client_secret" placeholder="<%= (google && google.secretSet) ? '•••••••• (ingesteld)': 'GOCSPX-…' %>" autocomplete="off">50 <span><%= t('agoog.client_secret') %> <% if (google && google.secretSet) { %><small style="font-weight:400">— <%= t('agoog.client_secret_set') %></small><% } %></span> 51 <input type="password" name="google_client_secret" placeholder="<%= (google && google.secretSet) ? t('agoog.secret_ph_set') : 'GOCSPX-…' %>" autocomplete="off"> 52 52 </label> 53 53 <div class="set-actions"> 54 <button type="submit" class="btn btn-primary"> Opslaan</button>54 <button type="submit" class="btn btn-primary"><%= t('agoog.save') %></button> 55 55 <% if (google && (google.clientId || google.secretSet)) { %> 56 <button type="submit" name="clear" value="1" class="btn"> Loskoppelen</button>56 <button type="submit" name="clear" value="1" class="btn"><%= t('agoog.disconnect') %></button> 57 57 <% } %> 58 58 </div> 59 59 </form> 60 60 <% if (typeof premiumEnabled !== 'undefined' && premiumEnabled && !isPremium) { %> 61 <p class="set-help" style="margin-top:.7rem; color:var(--accent)"> Premium-laag staat aan zonder Patreon — de Google-knop blijft verborgen tot premium ontgrendeld is.</p>61 <p class="set-help" style="margin-top:.7rem; color:var(--accent)"><%= t('agoog.premium_locked') %></p> 62 62 <% } %> 63 63 </section> -
src/views/pages/admin-help.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-page hb"> 2 <p><a href="/admin" class="btn">← Beheer</a></p>3 <h1> Handleiding</h1>4 <p class="hb-intro"> Uitleg van alle functies. Typ hieronder om te zoeken op een onderwerp of instructie.</p>2 <p><a href="/admin" class="btn">← <%= t('ahelp.back') %></a></p> 3 <h1><%= t('ahelp.title') %></h1> 4 <p class="hb-intro"><%= t('ahelp.intro') %></p> 5 5 6 6 <div class="hb-search"> 7 <input type="search" id="hb-q" placeholder=" Zoek… (bv. 'agenda', 'foto', 'cirkel', 'downloads')" autocomplete="off" aria-label="Zoek in de handleiding">7 <input type="search" id="hb-q" placeholder="<%= t('ahelp.search_placeholder') %>" autocomplete="off" aria-label="<%= t('ahelp.search_aria') %>"> 8 8 </div> 9 9 <p class="hb-count" id="hb-count" aria-live="polite"></p> … … 12 12 13 13 <section class="hb-item"> 14 <h2>✍️ Nieuwe post schrijven</h2>15 <p> Beheer → <strong>Nieuwe post</strong>. Geef een titel en schrijf je inhoud. Onderaan kies je de status: <em>concept</em> (niet zichtbaar) of <em>gepubliceerd</em>. Concepten staan bovenaan in je Beheer-overzicht zodat je ze terugvindt.</p>14 <h2>✍️ <%= t('ahelp.s_newpost_h') %></h2> 15 <p><%- t('ahelp.s_newpost_b') %></p> 16 16 </section> 17 17 18 18 <section class="hb-item"> 19 <h2>📝 Samenvatting & Cirkel Preview</h2>20 <p> Het veld <strong>Samenvatting & Cirkel Preview</strong> (de excerpt) is de korte previewtekst onder een post in lijsten, én de samenvatting die andere sites tonen als ze je post via een <strong>Cirkel</strong> overnemen. Laat je 'm leeg, dan wordt automatisch het begin van de post gebruikt.</p>19 <h2>📝 <%= t('ahelp.s_excerpt_h') %></h2> 20 <p><%- t('ahelp.s_excerpt_b') %></p> 21 21 </section> 22 22 23 23 <section class="hb-item"> 24 <h2>📌 Post pinnen / volgorde</h2>25 <p> In de post-editor kun je een post <strong>pinnen</strong> met een rang (1 = bovenaan). Gepinde posts staan vooraan in de tijdlijn/grid, op volgorde van hun rang. Rang leeg of 0 = niet gepind.</p>24 <h2>📌 <%= t('ahelp.s_pin_h') %></h2> 25 <p><%- t('ahelp.s_pin_b') %></p> 26 26 </section> 27 27 28 28 <section class="hb-item"> 29 <h2>🗓️ Publiceren plannen & alleen voor fans <small>(premium)</small></h2>30 <p> In de editor kun je een <strong>publicatiedatum</strong> in de toekomst zetten — de post verschijnt dan automatisch op dat moment. Met <strong>Alleen voor fans</strong> zien niet-ingelogde bezoekers alleen een teaser + login-uitnodiging; ingelogde fans zien alles.</p>29 <h2>🗓️ <%= t('ahelp.s_schedule_h') %> <small>(<%= t('ahelp.premium') %>)</small></h2> 30 <p><%- t('ahelp.s_schedule_b') %></p> 31 31 </section> 32 32 33 33 <section class="hb-item"> 34 <h2>🖼️ Afbeeldingen in posts</h2>35 <p> Afbeeldingen in de tekst en de cover-afbeelding worden altijd <strong>volledig</strong> getoond op de volledige breedte (niet bijgesneden), met hun natuurlijke hoogte.</p>34 <h2>🖼️ <%= t('ahelp.s_images_h') %></h2> 35 <p><%- t('ahelp.s_images_b') %></p> 36 36 </section> 37 37 38 38 <section class="hb-item"> 39 <h2>🎵 Audio & nummers toevoegen</h2>40 <p> Beheer → <strong>Audio</strong>. Upload een bestand of voeg een <em>link-only</em> nummer toe (zonder upload, alleen "open in"-links). Per nummer vul je titel, artiest, cover, en optioneel album/positie in. In een post toon je een nummer met de shortcode <code>[[track:id]]</code>, een album met <code>[[album:Naam]]</code>, een playlist met <code>[[playlist:id]]</code>.</p>39 <h2>🎵 <%= t('ahelp.s_audio_h') %></h2> 40 <p><%- t('ahelp.s_audio_b') %></p> 41 41 </section> 42 42 43 43 <section class="hb-item"> 44 <h2>© Credit, licentie & "open in"</h2>45 <p> Per nummer kun je een <strong>eigenaar/credit</strong> (met © -knop) en een <strong>licentie</strong> instellen — die worden ook in de mp3-metadata geschreven. Met de <strong>open-in</strong>-velden (Spotify / YouTube / SoundCloud) verschijnen knoppen om het nummer op die platforms te openen.</p>44 <h2>© <%= t('ahelp.s_credit_h') %></h2> 45 <p><%- t('ahelp.s_credit_b') %></p> 46 46 </section> 47 47 48 48 <section class="hb-item"> 49 <h2>⬇ Downloads <small>(premium)</small></h2>50 <p> Markeer een nummer als <strong>downloadbaar</strong> in Beheer → Audio (⬇-knop). Bezoekers vinden ze op de <strong>/downloads</strong>-pagina en laten hun e-mail achter om het bestand te krijgen (komt op je mailinglijst). Wil je downloads prominent in de feed? Maak een gewone post met slug <code>downloads</code> en pin 'm.</p>49 <h2>⬇ <%= t('ahelp.s_downloads_h') %> <small>(<%= t('ahelp.premium') %>)</small></h2> 50 <p><%- t('ahelp.s_downloads_b') %></p> 51 51 </section> 52 52 53 53 <section class="hb-item"> 54 <h2>📃 Albums & playlists</h2>55 <p> Geef nummers hetzelfde <strong>album</strong> + een <strong>positie</strong> om een album te vormen. Playlists maak je in Beheer → <strong>Playlists</strong>. Beide toon je in een post met <code>[[album:Naam]]</code> of <code>[[playlist:id]]</code>.</p>54 <h2>📃 <%= t('ahelp.s_albums_h') %></h2> 55 <p><%- t('ahelp.s_albums_b') %></p> 56 56 </section> 57 57 58 58 <section class="hb-item"> 59 <h2>📅 Agenda / evenementen <small>(premium)</small></h2>60 <p> Beheer → <strong>Agenda</strong>. Zet bovenaan <strong>"Agenda tonen op de site"</strong> aan — dan verschijnt de Agenda-knop in de balk en is de agendapagina bereikbaar. Voeg evenementen toe (datum, plaats, locatie, tickets). Bezoekers kunnen zich (los van de nieuwsbrief) aanmelden voor een seintje bij een nieuw evenement.</p>59 <h2>📅 <%= t('ahelp.s_agenda_h') %> <small>(<%= t('ahelp.premium') %>)</small></h2> 60 <p><%- t('ahelp.s_agenda_b') %></p> 61 61 </section> 62 62 63 63 <section class="hb-item"> 64 <h2>📰 Perskit <small>(premium)</small></h2>65 <p> Een deelbare perspagina op <strong>/pers</strong>. Bewerk 'm via de <strong>✎ Bewerken</strong>-knop op die pagina zelf (alleen jij ziet die). Stel een korte pers-bio + contact in, en kies <strong>tot 5 nummers</strong> die getoond worden (of laat leeg = automatisch de top-5 meest beluisterd).</p>64 <h2>📰 <%= t('ahelp.s_presskit_h') %> <small>(<%= t('ahelp.premium') %>)</small></h2> 65 <p><%- t('ahelp.s_presskit_b') %></p> 66 66 </section> 67 67 68 68 <section class="hb-item"> 69 <h2>🔗 Cirkels (federatie)</h2>70 <p> In Beheer → Instellingen zet je je site op <strong>Cirkel</strong>-modus. Daarna voeg je in Beheer → <strong>Cirkel</strong> andere Klonkt-sites toe; hun publieke posts verschijnen dan in je <strong>/cirkel</strong>-feed. Wat anderen van jóu zien is de titel + de "Samenvatting & Cirkel Preview". Hubs doen niet mee aan cirkels (alleen solo-sites).</p>69 <h2>🔗 <%= t('ahelp.s_circles_h') %></h2> 70 <p><%- t('ahelp.s_circles_b') %></p> 71 71 </section> 72 72 73 73 <section class="hb-item"> 74 <h2>📊 Statistieken <small>(premium)</small></h2>75 <p> Beheer → <strong>Statistieken</strong>. Cookievrij gemeten. <strong>Bezoeker-dagen</strong> = unieke bezoekers per dag, opgeteld (géén aantal personen). <strong>Weergaven</strong> = home/feed- en post-loads. Beheerder-bezoeken en bots tellen niet mee. Goed voor trends; absolute aantallen met een korrel zout.</p>74 <h2>📊 <%= t('ahelp.s_stats_h') %> <small>(<%= t('ahelp.premium') %>)</small></h2> 75 <p><%- t('ahelp.s_stats_b') %></p> 76 76 </section> 77 77 78 78 <section class="hb-item"> 79 <h2>✉️ Nieuwsbrief <small>(premium)</small></h2>80 <p> Beheer → <strong>Nieuwsbrief</strong>: stel een bericht op en stuur het naar je bevestigde abonnees. Bezoekers melden zich aan via de footer of <strong>/nieuwsbrief</strong>. Versturen vereist dat e-mail (SMTP) is ingesteld.</p>79 <h2>✉️ <%= t('ahelp.s_newsletter_h') %> <small>(<%= t('ahelp.premium') %>)</small></h2> 80 <p><%- t('ahelp.s_newsletter_b') %></p> 81 81 </section> 82 82 83 83 <section class="hb-item"> 84 <h2>🔗 Link-in-bio <small>(premium)</small></h2>85 <p> Een Linktree-achtige pagina op <strong>/links</strong> met je profiel-links. De kliks per link zie je terug in Statistieken.</p>84 <h2>🔗 <%= t('ahelp.s_linkbio_h') %> <small>(<%= t('ahelp.premium') %>)</small></h2> 85 <p><%- t('ahelp.s_linkbio_b') %></p> 86 86 </section> 87 87 88 88 <section class="hb-item"> 89 <h2>▶️ Embedbare speler <small>(premium)</small></h2>90 <p> Beheer → Audio toont een kopieerbare <code><iframe></code>-code (<strong>/embed</strong>) waarmee je je speler op een andere website kunt insluiten.</p>89 <h2>▶️ <%= t('ahelp.s_embed_h') %> <small>(<%= t('ahelp.premium') %>)</small></h2> 90 <p><%- t('ahelp.s_embed_b') %></p> 91 91 </section> 92 92 93 93 <section class="hb-item"> 94 <h2>🎨 Uiterlijk (thema, foto, accent)</h2>95 <p> Beheer → <strong>Uiterlijk</strong>: stel je sitenaam, tagline, profielfoto, accentkleur en kleurpalet in, en de standaard feed-weergave (Tijdlijn of Grid).</p>94 <h2>🎨 <%= t('ahelp.s_appearance_h') %></h2> 95 <p><%- t('ahelp.s_appearance_b') %></p> 96 96 </section> 97 97 98 98 <section class="hb-item"> 99 <h2>👥 Solo- / Hub-modus</h2>100 <p> Beheer → <strong>Instellingen</strong>: <em>Solo</em> = één site (jij). <em>Hub</em> = een bedrijfs-/labelsite met meerdere makers, elk een eigen Klonkt onder <code>/user/<naam></code>. <em>Cirkel</em> = solo + federatie met andere sites.</p>99 <h2>👥 <%= t('ahelp.s_tenancy_h') %></h2> 100 <p><%- t('ahelp.s_tenancy_b') %></p> 101 101 </section> 102 102 103 103 <section class="hb-item"> 104 <h2>⭐ Premium / Patreon</h2>105 <p> Premium-functies (Hub, Statistieken, Agenda, Downloads, Perskit, Nieuwsbrief, Link-in-bio, Embed, fan-login) ontgrendel je in Beheer → Instellingen door Patreon te koppelen ($10 lifetime). Updates en de kernapp blijven altijd gratis.</p>104 <h2>⭐ <%= t('ahelp.s_premium_h') %></h2> 105 <p><%- t('ahelp.s_premium_b') %></p> 106 106 </section> 107 107 108 108 <section class="hb-item"> 109 <h2>🔑 Google-login (bezoekers)</h2>110 <p> Beheer → Instellingen → <strong>Google</strong>. Optioneel: laat bezoekers/fans met Google inloggen om te reageren. Jij beheert met je wachtwoord — Google geeft nooit beheerrechten. Vul je eigen Google-client-gegevens in (per site).</p>109 <h2>🔑 <%= t('ahelp.s_google_h') %></h2> 110 <p><%- t('ahelp.s_google_b') %></p> 111 111 </section> 112 112 113 113 <section class="hb-item"> 114 <h2>🔄 Updates</h2>115 <p> Beheer → <strong>Updates</strong> (alleen god) toont de huidige versie en of er een nieuwere is. Met "Nu bijwerken" haal je de laatste versie binnen.</p>114 <h2>🔄 <%= t('ahelp.s_updates_h') %></h2> 115 <p><%- t('ahelp.s_updates_b') %></p> 116 116 </section> 117 117 118 118 </div> 119 119 120 <p class="hb-empty" id="hb-empty" hidden> Geen onderwerpen gevonden voor je zoekopdracht.</p>120 <p class="hb-empty" id="hb-empty" hidden><%= t('ahelp.empty') %></p> 121 121 </div> 122 122 -
src/views/pages/admin-newsletter.ejs
r929d98d rbbf9d1a 7 7 %> 8 8 <section class="anl"> 9 <h1> Nieuwsbrief</h1>9 <h1><%= t('anews.title') %></h1> 10 10 11 11 <% if (msg) { %><div class="anl-msg anl-<%= msgKind %>"><%= msg %></div><% } %> 12 12 13 13 <div class="anl-stats"> 14 <div class="anl-stat"><span class="anl-n"><%= c.confirmed %></span><span class="anl-l"> bevestigd</span></div>15 <div class="anl-stat"><span class="anl-n"><%= c.pending %></span><span class="anl-l"> in afwachting</span></div>16 <div class="anl-stat"><span class="anl-n"><%= c.unsub %></span><span class="anl-l"> uitgeschreven</span></div>14 <div class="anl-stat"><span class="anl-n"><%= c.confirmed %></span><span class="anl-l"><%= t('anews.confirmed') %></span></div> 15 <div class="anl-stat"><span class="anl-n"><%= c.pending %></span><span class="anl-l"><%= t('anews.pending') %></span></div> 16 <div class="anl-stat"><span class="anl-n"><%= c.unsub %></span><span class="anl-l"><%= t('anews.unsub') %></span></div> 17 17 </div> 18 18 19 19 <% if (!smtp) { %> 20 <div class="anl-msg anl-warn"> ⚠ SMTP is nog niet ingesteld. Aanmeldingen worden wél verzameld, maar versturen kan pas als je SMTP-gegevens invult (<code>SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM</code> in<code>.env</code>).</div>20 <div class="anl-msg anl-warn"><%= t('anews.smtp_warn_1') %> (<code>SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM</code> <%= t('anews.smtp_warn_2') %> <code>.env</code>).</div> 21 21 <% } %> 22 22 23 <p class="anl-share"> Aanmeldlink om te delen:<a href="<%= nlSubscribeUrl %>"><%= nlSubscribeUrl %></a></p>23 <p class="anl-share"><%= t('anews.share') %> <a href="<%= nlSubscribeUrl %>"><%= nlSubscribeUrl %></a></p> 24 24 25 <% var _sendConfirm = t('anews.send_confirm', { n: c.confirmed }); %> 25 26 <form method="POST" action="<%= siteUrlBase %>/admin/newsletter/send" class="anl-form"> 26 <label> Onderwerp27 <input type="text" name="subject" required maxlength="200" value="<%= (typeof nlSubject!=='undefined')?nlSubject:'' %>" placeholder=" Nieuwe single uit!">27 <label><%= t('anews.subject') %> 28 <input type="text" name="subject" required maxlength="200" value="<%= (typeof nlSubject!=='undefined')?nlSubject:'' %>" placeholder="<%= t('anews.subject_ph') %>"> 28 29 </label> 29 <label> Bericht30 <textarea name="body" rows="10" required placeholder=" Schrijf je update…"><%= (typeof nlBody!=='undefined')?nlBody:'' %></textarea>30 <label><%= t('anews.body') %> 31 <textarea name="body" rows="10" required placeholder="<%= t('anews.body_ph') %>"><%= (typeof nlBody!=='undefined')?nlBody:'' %></textarea> 31 32 </label> 32 <button type="submit" class="anl-send" <%= smtp ? '' : 'disabled' %> onclick="return confirm(' Nieuwsbrief versturen naar <%= c.confirmed %> bevestigde abonnee(s)?')">Versturen naar <%= c.confirmed %> abonnee(s)</button>33 <button type="submit" class="anl-send" <%= smtp ? '' : 'disabled' %> onclick="return confirm('<%= _sendConfirm %>')"><%= t('anews.send_btn', { n: c.confirmed }) %></button> 33 34 </form> 34 35 35 36 <% if (hist.length) { %> 36 <h2 class="anl-h2">Verstuurd</h2> 37 <h2 class="anl-h2"><%= t('anews.sent_heading') %></h2> 38 <% var _recipients = t('anews.recipients'); %> 37 39 <ul class="anl-hist"> 38 40 <% hist.forEach(function(h){ %> 39 <li><span class="anl-hist-subj"><%= h.subject %></span><span class="anl-hist-meta"><%= h.recipient_count %> ontvanger(s)· <%= formatDateTime ? formatDateTime(h.sent_at) : h.sent_at %></span></li>41 <li><span class="anl-hist-subj"><%= h.subject %></span><span class="anl-hist-meta"><%= h.recipient_count %> <%= _recipients %> · <%= formatDateTime ? formatDateTime(h.sent_at) : h.sent_at %></span></li> 40 42 <% }); %> 41 43 </ul> -
src/views/pages/admin-playlists.ejs
r929d98d rbbf9d1a 10 10 <header class="ax-header"> 11 11 <div> 12 <h1> Playlists</h1>12 <h1><%= t('apl.title') %></h1> 13 13 <p class="ax-tagline"> 14 Canonieke playlists. Bewerk een playlist hier en de wijzigingen werken 15 door in álle posts die hem gebruiken via <code>[[playlist:<id>]]</code>. 14 <%= t('apl.tagline_pre') %> <code>[[playlist:<id>]]</code><%= t('apl.tagline_post') %> 16 15 </p> 17 16 </div> … … 21 20 <div class="ax-actions-bar"> 22 21 <button type="button" class="ax-btn ax-btn-primary" id="pl-new-btn"> 23 + Nieuwe playlist22 + <%= t('apl.new_playlist') %> 24 23 </button> 25 24 </div> … … 29 28 <div class="ax-empty"> 30 29 <div class="ax-empty-icon">📃</div> 31 <p> Nog geen playlists.</p>32 <p class="ax-empty-sub"> Maak er een via de knop hierboven, of via de 📃 knop in de post-editor.</p>30 <p><%= t('apl.none') %></p> 31 <p class="ax-empty-sub"><%= t('apl.none_sub') %></p> 33 32 </div> 34 33 </div> 35 34 <% } else { %> 35 <% 36 // Pre-fetch loop labels (defensive — keep `t` calls out of the loop body). 37 var _aplPlaylist = t('apl.pill_playlist'); 38 var _aplAlbum = t('apl.pill_album'); 39 var _aplTrack = t('apl.track'); 40 var _aplTracks = t('apl.tracks'); 41 var _aplCopy = t('apl.copy_click'); 42 var _aplEdit = t('apl.edit'); 43 var _aplDelete = t('apl.delete'); 44 %> 36 45 <ul class="ax-list"> 37 46 <% playlists.forEach(function(p) { %> … … 44 53 <div class="ax-track-title"> 45 54 <%= p.title %> 46 <span class="ax-pill"><%= p.kind === 'playlist' ? '📃 playlist' : '💿 album'%></span>55 <span class="ax-pill"><%= p.kind === 'playlist' ? '📃 ' + _aplPlaylist : '💿 ' + _aplAlbum %></span> 47 56 </div> 48 57 <div class="ax-track-sub"> 49 <span><%= p.track_count %> <%= p.track_count === 1 ? 'track' : 'tracks'%></span>58 <span><%= p.track_count %> <%= p.track_count === 1 ? _aplTrack : _aplTracks %></span> 50 59 <% if (p.artist) { %><span class="ax-track-sep">·</span><span><%= p.artist %></span><% } %> 51 60 <% if (p.year) { %><span class="ax-track-sep">·</span><span><%= p.year %></span><% } %> 52 61 </div> 53 <code class="ax-embed" data-copy="[[playlist:<%= p.id %>]]" title=" Klik om te kopiëren">[[playlist:<%= p.id %>]]</code>62 <code class="ax-embed" data-copy="[[playlist:<%= p.id %>]]" title="<%= _aplCopy %>">[[playlist:<%= p.id %>]]</code> 54 63 </div> 55 64 56 65 <div class="ax-track-actions"> 57 <button type="button" class="ax-icon-btn" data-pl-edit data-id="<%= p.id %>" aria-label=" Bewerken" title="Bewerken">✎</button>58 <button type="button" class="ax-icon-btn ax-icon-btn-danger" data-pl-delete data-id="<%= p.id %>" data-title="<%= p.title %>" aria-label=" Verwijderen" title="Verwijderen">🗑</button>66 <button type="button" class="ax-icon-btn" data-pl-edit data-id="<%= p.id %>" aria-label="<%= _aplEdit %>" title="<%= _aplEdit %>">✎</button> 67 <button type="button" class="ax-icon-btn ax-icon-btn-danger" data-pl-delete data-id="<%= p.id %>" data-title="<%= p.title %>" aria-label="<%= _aplDelete %>" title="<%= _aplDelete %>">🗑</button> 59 68 </div> 60 69 </li> … … 229 238 el.classList.add('is-copied'); 230 239 const original = el.textContent; 231 el.textContent = '✓ gekopieerd';240 el.textContent = '✓ <%= t('apl.copied') %>'; 232 241 setTimeout(() => { el.classList.remove('is-copied'); el.textContent = original; }, 1200); 233 242 } catch (_) { /* fall back to selection */ } … … 247 256 const id = btn.dataset.id; 248 257 const title = btn.dataset.title || id; 249 if (!confirm( `Playlist "${title}" verwijderen? Posts die deze playlist embedden tonen vanaf nu een placeholder.`)) return;258 if (!confirm('<%= t('apl.delete_confirm') %>'.replace('{title}', title))) return; 250 259 try { 251 260 const r = await fetch(`/admin/playlists/api/${encodeURIComponent(id)}/delete`, { … … 256 265 const j = await r.json(); 257 266 if (j.ok) location.reload(); 258 else alert(' Verwijderen mislukt: ' + (j.error || ''));267 else alert('<%= t('apl.delete_failed') %>: ' + (j.error || '')); 259 268 } catch (err) { 260 alert(' Verwijderen mislukt: ' + err.message);269 alert('<%= t('apl.delete_failed') %>: ' + err.message); 261 270 } 262 271 }); -
src/views/pages/admin-seo.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-site-edit-page"> 2 <p><a href="/admin" class="btn">← Beheer</a></p>3 <h1> SEO & vindbaarheid</h1>4 <p class="admin-tagline"> Geavanceerde SEO van <strong><%= site.title %></strong> — hoe je site in zoekmachines en bij het delen op social media verschijnt.</p>2 <p><a href="/admin" class="btn">← <%= t('aseo.back') %></a></p> 3 <h1><%= t('aseo.title') %></h1> 4 <p class="admin-tagline"><%= t('aseo.tagline_pre') %> <strong><%= site.title %></strong> <%= t('aseo.tagline_post') %></p> 5 5 6 6 <% if (typeof success !== 'undefined' && success) { %><div class="audio-flash audio-flash--ok"><%= success %></div><% } %> … … 10 10 11 11 <fieldset> 12 <legend> Indexeren</legend>12 <legend><%= t('aseo.index_legend') %></legend> 13 13 <label class="cb"> 14 14 <input type="checkbox" name="robots_index" value="1" <%= site.robots_index ? 'checked' : '' %>> 15 <span> Zoekmachines mogen deze site indexeren <small class="form-hint-inline">— uit = <code>noindex</code> + sitemap.xml verborgen</small></span>15 <span><%= t('aseo.index_label') %> <small class="form-hint-inline">— <%= t('aseo.index_hint_pre') %> <code>noindex</code> <%= t('aseo.index_hint_post') %></small></span> 16 16 </label> 17 17 </fieldset> 18 18 19 19 <fieldset> 20 <legend> Titel & omschrijving</legend>20 <legend><%= t('aseo.title_legend') %></legend> 21 21 <label> 22 <span> Titel-sjabloon <small class="form-hint-inline">— gebruik <code>{title}</code> en<code>{site}</code></small></span>22 <span><%= t('aseo.title_template') %> <small class="form-hint-inline">— <%= t('aseo.title_template_hint_pre') %> <code>{title}</code> <%= t('aseo.title_template_hint_and') %> <code>{site}</code></small></span> 23 23 <input type="text" name="title_template" value="<%= site.title_template || '{title} — {site}' %>" maxlength="200" placeholder="{title} — {site}"> 24 24 </label> 25 25 <label> 26 <span> Standaard-omschrijving <small class="form-hint-inline">— meta description / og:description als een pagina er geen heeft</small></span>27 <textarea name="default_description" rows="2" maxlength="500" placeholder=" Korte omschrijving van je site (max ~160 tekens werkt het best)"><%= site.default_description || '' %></textarea>26 <span><%= t('aseo.default_desc') %> <small class="form-hint-inline">— <%= t('aseo.default_desc_hint') %></small></span> 27 <textarea name="default_description" rows="2" maxlength="500" placeholder="<%= t('aseo.default_desc_ph') %>"><%= site.default_description || '' %></textarea> 28 28 </label> 29 29 <label> 30 <span> Canonieke basis-URL <small class="form-hint-inline">— de productie-HTTPS-URL, voorkomt dubbele-content-straf</small></span>30 <span><%= t('aseo.canonical') %> <small class="form-hint-inline">— <%= t('aseo.canonical_hint') %></small></span> 31 31 <input type="text" name="canonical" value="<%= site.canonical || '' %>" placeholder="https://jouw-domein.com" maxlength="200"> 32 32 </label> 33 33 <label> 34 <span> Auteur <small class="form-hint-inline">— meta author-tag</small></span>35 <input type="text" name="author" value="<%= site.author || '' %>" maxlength="120" placeholder=" Jouw naam">34 <span><%= t('aseo.author') %> <small class="form-hint-inline">— <%= t('aseo.author_hint') %></small></span> 35 <input type="text" name="author" value="<%= site.author || '' %>" maxlength="120" placeholder="<%= t('aseo.author_ph') %>"> 36 36 </label> 37 37 </fieldset> 38 38 39 39 <fieldset> 40 <legend> Delen op social media</legend>40 <legend><%= t('aseo.social_legend') %></legend> 41 41 <label> 42 <span> Standaard deel-afbeelding (URL) <small class="form-hint-inline">— og:image / Twitter-card; ~1200×630px</small></span>42 <span><%= t('aseo.og_image') %> <small class="form-hint-inline">— <%= t('aseo.og_image_hint') %></small></span> 43 43 <input type="text" name="og_image_default" value="<%= site.og_image_default || '' %>" placeholder="https://jouw-domein.com/share.jpg" maxlength="500"> 44 44 </label> 45 45 <label> 46 <span> Taal-locale <small class="form-hint-inline">— og:locale, bv. <code>nl_NL</code> of<code>en_US</code></small></span>46 <span><%= t('aseo.og_locale') %> <small class="form-hint-inline">— <%= t('aseo.og_locale_hint_pre') %> <code>nl_NL</code> <%= t('aseo.og_locale_hint_or') %> <code>en_US</code></small></span> 47 47 <input type="text" name="og_locale" value="<%= site.og_locale || '' %>" placeholder="nl_NL" maxlength="32"> 48 48 </label> 49 49 <label> 50 <span> Twitter / X-handle <small class="form-hint-inline">— met @</small></span>50 <span><%= t('aseo.twitter') %> <small class="form-hint-inline">— <%= t('aseo.twitter_hint') %></small></span> 51 51 <input type="text" name="twitter" value="<%= site.twitter || '' %>" placeholder="@jouwnaam" maxlength="64"> 52 52 </label> 53 53 <label> 54 <span> Facebook App-ID <small class="form-hint-inline">— fb:app_id (optioneel)</small></span>54 <span><%= t('aseo.fb_app') %> <small class="form-hint-inline">— <%= t('aseo.fb_app_hint') %></small></span> 55 55 <input type="text" name="facebook_app_id" value="<%= site.facebook_app_id || '' %>" maxlength="64"> 56 56 </label> … … 58 58 59 59 <fieldset> 60 <legend> Uitgever (JSON-LD / rich results)</legend>60 <legend><%= t('aseo.publisher_legend') %></legend> 61 61 <label> 62 <span> Type</span>62 <span><%= t('aseo.type') %></span> 63 63 <select name="schema_type"> 64 <option value="Person" <%= site.schema_type === 'Organization' ? '' : 'selected' %>> Persoon</option>65 <option value="Organization" <%= site.schema_type === 'Organization' ? 'selected' : '' %>> Organisatie / bedrijf</option>64 <option value="Person" <%= site.schema_type === 'Organization' ? '' : 'selected' %>><%= t('aseo.type_person') %></option> 65 <option value="Organization" <%= site.schema_type === 'Organization' ? 'selected' : '' %>><%= t('aseo.type_org') %></option> 66 66 </select> 67 67 </label> 68 68 <label> 69 <span> Naam <small class="form-hint-inline">— valt terug op de site-titel</small></span>69 <span><%= t('aseo.publisher_name') %> <small class="form-hint-inline">— <%= t('aseo.publisher_name_hint') %></small></span> 70 70 <input type="text" name="publisher_name" value="<%= site.publisher_name || '' %>" maxlength="200"> 71 71 </label> 72 72 <label> 73 <span> URL</span>73 <span><%= t('aseo.publisher_url') %></span> 74 74 <input type="text" name="publisher_url" value="<%= site.publisher_url || '' %>" placeholder="https://jouw-domein.com" maxlength="200"> 75 75 </label> 76 76 <label> 77 <span> Logo (URL)</span>77 <span><%= t('aseo.publisher_logo') %></span> 78 78 <input type="text" name="publisher_logo" value="<%= site.publisher_logo || '' %>" placeholder="https://jouw-domein.com/logo.png" maxlength="500"> 79 79 </label> … … 81 81 82 82 <fieldset> 83 <legend> Zoekmachine-verificatie</legend>83 <legend><%= t('aseo.verify_legend') %></legend> 84 84 <label> 85 <span> Google site-verificatie</span>85 <span><%= t('aseo.verify_google') %></span> 86 86 <input type="text" name="google_verification" value="<%= site.google_verification || '' %>" maxlength="200"> 87 87 </label> 88 88 <label> 89 <span> Bing <small class="form-hint-inline">— msvalidate.01</small></span>89 <span><%= t('aseo.verify_bing') %> <small class="form-hint-inline">— <%= t('aseo.verify_bing_hint') %></small></span> 90 90 <input type="text" name="bing_verification" value="<%= site.bing_verification || '' %>" maxlength="200"> 91 91 </label> 92 92 <label> 93 <span> Pinterest <small class="form-hint-inline">— p:domain_verify</small></span>93 <span><%= t('aseo.verify_pinterest') %> <small class="form-hint-inline">— <%= t('aseo.verify_pinterest_hint') %></small></span> 94 94 <input type="text" name="pinterest_verification" value="<%= site.pinterest_verification || '' %>" maxlength="200"> 95 95 </label> 96 96 <label> 97 <span> Yandex</span>97 <span><%= t('aseo.verify_yandex') %></span> 98 98 <input type="text" name="yandex_verification" value="<%= site.yandex_verification || '' %>" maxlength="200"> 99 99 </label> 100 100 </fieldset> 101 101 102 <button type="submit" class="btn btn-primary"> SEO opslaan</button>102 <button type="submit" class="btn btn-primary"><%= t('aseo.save') %></button> 103 103 </form> 104 104 </div> -
src/views/pages/admin-settings.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-page"> 2 <h1> Instellingen</h1>3 <p><a href="/admin" class="btn">← Beheer</a></p>2 <h1><%= t('aset.title') %></h1> 3 <p><a href="/admin" class="btn">← <%= t('aset.back_admin') %></a></p> 4 4 5 5 <% if (success) { %><div class="alert alert-success"><%= success %></div><% } %> … … 7 7 8 8 <section class="set-card"> 9 <h2> Modus</h2>9 <h2><%= t('aset.mode') %></h2> 10 10 <p class="set-help"> 11 Bepaalt hoe deze installatie werkt. Wisselen is veilig: er wordt niets verwijderd — 12 Solo verbergt alleen de multi-onderdelen en toont je primaire site. 11 <%= t('aset.mode_help') %> 13 12 </p> 14 13 … … 17 16 <input type="radio" name="tenancy" value="solo" <%= tenancy === 'solo' ? 'checked' : '' %>> 18 17 <span> 19 <strong> Solo</strong> — één site (de jouwe).20 <small> Geen gebruikers-directory, geen site-wissel. Bezoekers kunnen wel met Google inloggen om te reageren.</small>18 <strong><%= t('aset.solo') %></strong> — <%= t('aset.solo_title') %> 19 <small><%= t('aset.solo_desc') %></small> 21 20 </span> 22 21 </label> … … 25 24 <input type="radio" name="tenancy" value="hub" <%= tenancy === 'hub' ? 'checked' : '' %> <%= _hubLocked ? 'disabled' : '' %>> 26 25 <span> 27 <strong> Hub</strong> — bedrijfssite met<code>/user/</code>.28 <% if (_hubLocked) { %><span style="display:inline-block;font-size:.7rem;font-weight:700;color:var(--accent);border:1px solid var(--accent);border-radius:999px;padding:.05rem .45rem;margin-left:.3rem;vertical-align:1px;">🔒 premium</span><% } %>29 <small> Jij (admin) wijst gebruikers een eigen Klonkt Hub toe. Gesloten cirkel: geen open registratie.<% if (_hubLocked) { %> <em>Vereist premium — koppel Patreon in deze pagina.</em><% } %></small>26 <strong><%= t('aset.hub') %></strong> — <%= t('aset.hub_title') %> <code>/user/</code>. 27 <% if (_hubLocked) { %><span style="display:inline-block;font-size:.7rem;font-weight:700;color:var(--accent);border:1px solid var(--accent);border-radius:999px;padding:.05rem .45rem;margin-left:.3rem;vertical-align:1px;">🔒 <%= t('aset.premium_badge') %></span><% } %> 28 <small><%= t('aset.hub_desc') %><% if (_hubLocked) { %> <em><%= t('aset.hub_locked_note') %></em><% } %></small> 30 29 </span> 31 30 </label> … … 33 32 <input type="radio" name="tenancy" value="circle" <%= tenancy === 'circle' ? 'checked' : '' %>> 34 33 <span> 35 <strong> Cirkels</strong> — solo + federatie.36 <small> Eén eigen site die de publieke posts van andere Klonkt-sites toont. Asymmetrisch: jij bepaalt wie in jouw cirkel zit.</small>37 </span> 38 </label> 39 <button type="submit" class="btn btn-primary"> Opslaan</button>34 <strong><%= t('aset.circle') %></strong> — <%= t('aset.circle_title') %> 35 <small><%= t('aset.circle_desc') %></small> 36 </span> 37 </label> 38 <button type="submit" class="btn btn-primary"><%= t('aset.save') %></button> 40 39 </form> 41 40 </section> … … 45 44 <% if (tenancy === 'hub') { %> 46 45 <section class="set-card set-card--mode" style="margin-top:1rem"> 47 <h2> Hub-hoofdpagina</h2>46 <h2><%= t('aset.hub_home') %></h2> 48 47 <p class="set-help"> 49 De openbare hub-pagina (<code>/</code>) is generiek — van geen enkele gebruiker. 50 Hier stel je de naam en intro in. Jouw eigen Klonkt Hub blijft los bestaan onder <code>/user/</code>. 48 <%= t('aset.hub_home_help_1') %> (<code>/</code>) <%= t('aset.hub_home_help_2') %> <code>/user/</code>. 51 49 </p> 52 50 <form method="post" action="/admin/settings" class="set-form" enctype="multipart/form-data"> 53 51 <label class="set-field"> 54 <span> Naam</span>55 <input type="text" name="hub_title" maxlength="80" value="<%= hubTitle %>" placeholder=" bijv. Studio Noord">56 </label> 57 <label class="set-field"> 58 <span> Tagline</span>59 <input type="text" name="hub_tagline" maxlength="120" value="<%= hubTagline %>" placeholder=" bijv. Onafhankelijk muzieklabel">60 </label> 61 <label class="set-field"> 62 <span> Intro</span>63 <textarea name="hub_intro" maxlength="400" rows="3" placeholder=" Korte introtekst onder de titel."><%= hubIntro %></textarea>64 </label> 65 <label class="set-field"> 66 <span> Hero-afbeelding (URL) <small style="font-weight:400;color:var(--ink-muted)">— optioneel; achtergrond van de hero</small></span>52 <span><%= t('aset.name') %></span> 53 <input type="text" name="hub_title" maxlength="80" value="<%= hubTitle %>" placeholder="<%= t('aset.name_ph') %>"> 54 </label> 55 <label class="set-field"> 56 <span><%= t('aset.tagline') %></span> 57 <input type="text" name="hub_tagline" maxlength="120" value="<%= hubTagline %>" placeholder="<%= t('aset.tagline_ph') %>"> 58 </label> 59 <label class="set-field"> 60 <span><%= t('aset.intro') %></span> 61 <textarea name="hub_intro" maxlength="400" rows="3" placeholder="<%= t('aset.intro_ph') %>"><%= hubIntro %></textarea> 62 </label> 63 <label class="set-field"> 64 <span><%= t('aset.hero_image') %> <small style="font-weight:400;color:var(--ink-muted)">— <%= t('aset.hero_image_hint') %></small></span> 67 65 <input type="text" name="hub_hero_image" maxlength="300" value="<%= hubHeroImage %>" placeholder="/media/... of https://..."> 68 66 </label> 69 67 <label class="set-field"> 70 <span> …of upload een afbeelding <small style="font-weight:400;color:var(--ink-muted)">— jpg/png/webp/gif, max 5 MB; vervangt de URL hierboven</small></span>68 <span><%= t('aset.hero_upload') %> <small style="font-weight:400;color:var(--ink-muted)">— <%= t('aset.hero_upload_hint') %></small></span> 71 69 <input type="file" name="hub_hero_file" accept="image/png,image/jpeg,image/webp,image/gif"> 72 70 </label> 73 71 <label class="set-field"> 74 <span> Donkere overlay <small style="font-weight:400;color:var(--ink-muted)">— maakt de hero donkerder zodat de tekst leesbaar blijft</small></span>72 <span><%= t('aset.hero_overlay') %> <small style="font-weight:400;color:var(--ink-muted)">— <%= t('aset.hero_overlay_hint') %></small></span> 75 73 <span style="display:flex;align-items:center;gap:0.75rem"> 76 74 <input type="range" name="hub_hero_overlay" min="0" max="100" step="5" value="<%= hubHeroOverlay %>" style="flex:1" … … 81 79 <% if (hubHeroImage) { %> 82 80 <div class="set-field"> 83 <span style="font-weight:400;color:var(--ink-muted);font-size:0.9rem"> Voorbeeld (met overlay):</span>81 <span style="font-weight:400;color:var(--ink-muted);font-size:0.9rem"><%= t('aset.preview_overlay') %></span> 84 82 <div style="position:relative;width:100%;aspect-ratio:16/7;margin-top:0.4rem;border-radius:8px;overflow:hidden;border:1px solid var(--rule);background:var(--paper-2)"> 85 <img src="<%= hubHeroImage %>" alt=" Hero-voorbeeld" style="display:block;width:100%;height:100%;object-fit:cover">83 <img src="<%= hubHeroImage %>" alt="<%= t('aset.hero_preview_alt') %>" style="display:block;width:100%;height:100%;object-fit:cover"> 86 84 <div id="hero-ov-preview" style="position:absolute;inset:0;background:#000;opacity:<%= (hubHeroOverlay/100).toFixed(2) %>;pointer-events:none"></div> 87 85 </div> 88 86 </div> 89 87 <% } %> 90 <button type="submit" class="btn btn-primary"> Opslaan</button>88 <button type="submit" class="btn btn-primary"><%= t('aset.save') %></button> 91 89 </form> 92 90 </section> … … 95 93 <% if (tenancy === 'circle') { %> 96 94 <section class="set-card set-card--mode" style="margin-top:1rem"> 97 <h2>🔗 Je cirkel</h2>95 <h2>🔗 <%= t('aset.your_circle') %></h2> 98 96 <p class="set-help"> 99 Beheer welke andere Klonkt-sites je in je cirkel toont, en of jouw site in 100 cirkels van anderen mag verschijnen. Asymmetrisch: jij bepaalt wie je volgt. 97 <%= t('aset.your_circle_help') %> 101 98 </p> 102 99 <div class="set-actions"> 103 <a href="/admin/circle" class="btn btn-primary"> Beheer je cirkel→</a>100 <a href="/admin/circle" class="btn btn-primary"><%= t('aset.manage_circle') %> →</a> 104 101 </div> 105 102 </section> … … 110 107 <% if (premium && premium.enabled) { %> 111 108 <section class="set-card" style="margin-top:1rem"> 112 <h2>Premium (Patreon)</h2> 113 <p class="set-help">Ontgrendel de premium-modules (Hub-modus, Statistieken, Fan-login, 114 Prutter-DMs) met je <strong>$10-lifetime</strong> Patreon-steun. De app en alle updates 115 blijven gratis.</p> 109 <h2><%= t('aset.premium') %></h2> 110 <p class="set-help"><%= t('aset.premium_help_1') %> <strong><%= t('aset.premium_lifetime') %></strong> <%= t('aset.premium_help_2') %></p> 116 111 <% if (premium.premium) { %> 117 <p class="set-help"><strong>✓ Premium actief.</strong>118 <% if (premium.supportCents) { %> Lifetime-steun:$<%= (premium.supportCents/100).toFixed(2) %>.<% } %></p>119 <a href="/admin/patreon/disconnect" class="btn"> Patreon ontkoppelen</a>112 <p class="set-help"><strong>✓ <%= t('aset.premium_active') %></strong> 113 <% if (premium.supportCents) { %><%= t('aset.lifetime_support') %> $<%= (premium.supportCents/100).toFixed(2) %>.<% } %></p> 114 <a href="/admin/patreon/disconnect" class="btn"><%= t('aset.patreon_disconnect') %></a> 120 115 <% } else if (premium.connected) { %> 121 <p class="set-help"> Patreon gekoppeld, maar nog geen $10 lifetime<% if (premium.supportCents) { %>122 ( nu $<%= (premium.supportCents/100).toFixed(2) %>)<% } %>. Steun de campagne en koppel opnieuw.</p>123 <a href="/admin/patreon/connect" class="btn btn-primary"> Opnieuw koppelen</a>116 <p class="set-help"><%= t('aset.patreon_no_lifetime') %><% if (premium.supportCents) { %> 117 (<%= t('aset.now') %> $<%= (premium.supportCents/100).toFixed(2) %>)<% } %>. <%= t('aset.patreon_support_again') %></p> 118 <a href="/admin/patreon/connect" class="btn btn-primary"><%= t('aset.patreon_reconnect') %></a> 124 119 <% } else { %> 125 <p class="set-help"> Nog niet gekoppeld.</p>126 <a href="/admin/patreon/connect" class="btn btn-primary"> Koppel Patreon</a>120 <p class="set-help"><%= t('aset.patreon_not_connected') %></p> 121 <a href="/admin/patreon/connect" class="btn btn-primary"><%= t('aset.patreon_connect') %></a> 127 122 <% } %> 128 123 </section> … … 131 126 <%# Google-login staat op een eigen Beheer-pagina (compacte link hier). %> 132 127 <section class="set-card" style="margin-top:1rem"> 133 <h2> Google-login (bezoekers)</h2>134 <p class="set-help"> Laat bezoekers met Google inloggen om te reageren — met je eigen Google Cloud-client.135 <% if (google && google.configured) { %><strong> Status: ingesteld ✓</strong><% } else { %>Nog niet ingesteld.<% } %></p>136 <div class="set-actions"><a href="/admin/settings/google" class="btn btn-primary"> Google-login instellen→</a></div>128 <h2><%= t('aset.google_login') %></h2> 129 <p class="set-help"><%= t('aset.google_login_help') %> 130 <% if (google && google.configured) { %><strong><%= t('aset.status_set') %> ✓</strong><% } else { %><%= t('aset.not_set_yet') %><% } %></p> 131 <div class="set-actions"><a href="/admin/settings/google" class="btn btn-primary"><%= t('aset.google_login_setup') %> →</a></div> 137 132 </section> 138 133 … … 140 135 <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %> 141 136 <section class="set-card" style="margin-top:1rem"> 142 <h2> Nieuwsbrief</h2>143 <p class="set-help"> Toon een <strong>aanmeldveld in de footer</strong> van je site zodat bezoekers zich op elke pagina kunnen inschrijven. (De volledige aanmeldpagina blijft op<code><%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/nieuwsbrief</code>.)</p>137 <h2><%= t('aset.newsletter') %></h2> 138 <p class="set-help"><%= t('aset.newsletter_help_1') %> <strong><%= t('aset.newsletter_footer_field') %></strong> <%= t('aset.newsletter_help_2') %> <code><%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/nieuwsbrief</code>.)</p> 144 139 <form method="post" action="/admin/settings/footer" class="set-form"> 145 140 <label class="set-toggle" style="display:flex;align-items:center;gap:.6rem;cursor:pointer"> 146 141 <input type="checkbox" name="footer_newsletter" value="1" <%= footerNewsletter ? 'checked' : '' %>> 147 <span> Aanmeldveld in de footer tonen</span>148 </label> 149 <div class="set-actions"><button type="submit" class="btn btn-primary"> Opslaan</button></div>142 <span><%= t('aset.newsletter_show_footer') %></span> 143 </label> 144 <div class="set-actions"><button type="submit" class="btn btn-primary"><%= t('aset.save') %></button></div> 150 145 </form> 151 146 </section> … … 154 149 <%# E-mail / SMTP — nodig voor nieuwsbrief versturen, notify-mails en wachtwoord-reset. %> 155 150 <section class="set-card" style="margin-top:1rem"> 156 <h2> E-mail (SMTP)</h2>157 <p class="set-help"> Nodig om de <strong>nieuwsbrief te versturen</strong>, <strong>show-notify</strong>-mails te sturen en wachtwoord-reset per mail te laten werken. Vul de gegevens van je mailprovider in (bv. je hostingmail, Gmail-app-wachtwoord, Brevo, Mailgun…).158 <% if (smtp && smtp.configured) { %><br><strong> Status: ingesteld ✓</strong><% if (smtp.fromEnv) { %> <small>(via .env)</small><% } %><% } else { %><br>Nog niet ingesteld — versturen werkt nog niet.<% } %></p>151 <h2><%= t('aset.email_smtp') %></h2> 152 <p class="set-help"><%= t('aset.smtp_help_1') %> <strong><%= t('aset.smtp_help_newsletter') %></strong><%= t('aset.smtp_help_2') %> <strong><%= t('aset.smtp_help_notify') %></strong><%= t('aset.smtp_help_3') %> 153 <% if (smtp && smtp.configured) { %><br><strong><%= t('aset.status_set') %> ✓</strong><% if (smtp.fromEnv) { %> <small>(<%= t('aset.via_env') %>)</small><% } %><% } else { %><br><%= t('aset.smtp_not_set') %><% } %></p> 159 154 160 155 <form method="post" action="/admin/settings/smtp" class="set-form"> 161 <label><span> SMTP-host</span>156 <label><span><%= t('aset.smtp_host') %></span> 162 157 <input type="text" name="smtp_host" value="<%= smtp ? smtp.host : '' %>" placeholder="smtp.jouwprovider.nl" autocomplete="off"> 163 158 </label> 164 <label><span> Poort <small style="font-weight:400">— 587 (STARTTLS) of 465 (SSL)</small></span>159 <label><span><%= t('aset.smtp_port') %> <small style="font-weight:400">— <%= t('aset.smtp_port_hint') %></small></span> 165 160 <input type="text" name="smtp_port" value="<%= (smtp && smtp.port) ? smtp.port : '' %>" placeholder="587" autocomplete="off" inputmode="numeric"> 166 161 </label> 167 <label><span> Gebruikersnaam</span>162 <label><span><%= t('aset.smtp_user') %></span> 168 163 <input type="text" name="smtp_user" value="<%= smtp ? smtp.user : '' %>" placeholder="jij@jouwprovider.nl" autocomplete="off"> 169 164 </label> 170 <label><span> Wachtwoord <% if (smtp && smtp.passSet) { %><small style="font-weight:400">— ingesteld; leeg laten = ongewijzigd</small><% } %></span>171 <input type="password" name="smtp_pass" placeholder="<%= (smtp && smtp.passSet) ? '•••••••• (ingesteld)' : 'app-wachtwoord'%>" autocomplete="off">172 </label> 173 <label><span> Afzender <small style="font-weight:400">— optioneel; standaard = gebruikersnaam</small></span>174 <input type="text" name="smtp_from" value="<%= smtp ? smtp.from : '' %>" placeholder=" Jouw Naam <jij@jouwprovider.nl>" autocomplete="off">165 <label><span><%= t('aset.smtp_pass') %> <% if (smtp && smtp.passSet) { %><small style="font-weight:400">— <%= t('aset.smtp_pass_set_hint') %></small><% } %></span> 166 <input type="password" name="smtp_pass" placeholder="<%= (smtp && smtp.passSet) ? t('aset.smtp_pass_ph_set') : t('aset.smtp_pass_ph') %>" autocomplete="off"> 167 </label> 168 <label><span><%= t('aset.smtp_from') %> <small style="font-weight:400">— <%= t('aset.smtp_from_hint') %></small></span> 169 <input type="text" name="smtp_from" value="<%= smtp ? smtp.from : '' %>" placeholder="<%= t('aset.smtp_from_ph') %>" autocomplete="off"> 175 170 </label> 176 171 <div class="set-actions"> 177 <button type="submit" class="btn btn-primary"> SMTP opslaan</button>172 <button type="submit" class="btn btn-primary"><%= t('aset.smtp_save') %></button> 178 173 <% if (smtp && (smtp.host || smtp.passSet)) { %> 179 <button type="submit" name="clear" value="1" class="btn" formnovalidate> Wissen</button>174 <button type="submit" name="clear" value="1" class="btn" formnovalidate><%= t('aset.clear') %></button> 180 175 <% } %> 181 176 </div> … … 184 179 <% if (smtp && smtp.configured) { %> 185 180 <form method="post" action="/admin/settings/smtp/test" class="set-form" style="margin-top:.8rem"> 186 <label><span> Testmail sturen naar</span>181 <label><span><%= t('aset.test_mail_to') %></span> 187 182 <input type="email" name="to" placeholder="jij@email.nl" autocomplete="off"> 188 183 </label> 189 <div class="set-actions"><button type="submit" class="btn">✉ Testmail sturen</button></div>184 <div class="set-actions"><button type="submit" class="btn">✉ <%= t('aset.send_test_mail') %></button></div> 190 185 </form> 191 186 <% } %> -
src/views/pages/admin-shows.ejs
r929d98d rbbf9d1a 6 6 %> 7 7 <section class="ash"> 8 <p><a href="/admin" class="btn">← Beheer</a></p>9 <h1> Agenda</h1>8 <p><a href="/admin" class="btn">← <%= t('ashow.back_admin') %></a></p> 9 <h1><%= t('ashow.title') %></h1> 10 10 11 11 <% if (msg) { %><div class="ash-msg ash-<%= (typeof msgKind!=='undefined')?msgKind:'' %>"><%= msg %></div><% } %> … … 15 15 <label style="display:flex;align-items:center;gap:10px;cursor:pointer;font-weight:600"> 16 16 <input type="checkbox" name="enabled" value="1" <%= agOn ? 'checked' : '' %> onchange="this.form.submit()" style="width:18px;height:18px;accent-color:var(--accent)"> 17 <span> Agenda tonen op de site <small style="font-weight:400;opacity:.7">(Agenda-knop in de balk + de agendapagina)</small></span>17 <span><%= t('ashow.show_toggle') %> <small style="font-weight:400;opacity:.7"><%= t('ashow.show_toggle_hint') %></small></span> 18 18 </label> 19 <noscript><button type="submit" class="btn"> Opslaan</button></noscript>19 <noscript><button type="submit" class="btn"><%= t('ashow.save') %></button></noscript> 20 20 </form> 21 <% if (!agOn) { %><div class="ash-msg"> De agenda staat nu <strong>uit</strong> — bezoekers zien geen Agenda-knop en de agendapagina is niet bereikbaar. Zet 'm aan om je evenementen te tonen.</div><% } %>21 <% if (!agOn) { %><div class="ash-msg"><%= t('ashow.off_notice_1') %> <strong><%= t('ashow.off') %></strong> <%= t('ashow.off_notice_2') %></div><% } %> 22 22 23 <p class="ash-note"><strong><%= nc %></strong> abonnee(s) voor evenement-aankondigingen.24 <% if (!smtp) { %><br> ⚠ SMTP niet ingesteld — evenementen worden opgeslagen, maar notify-mails kunnen pas verstuurd worden zodra je SMTP invult.<% } %></p>23 <p class="ash-note"><strong><%= nc %></strong> <%= t('ashow.subscribers') %> 24 <% if (!smtp) { %><br><%= t('ashow.smtp_warn') %><% } %></p> 25 25 26 26 <form method="POST" action="<%= siteUrlBase %>/admin/shows" class="ash-form"> 27 27 <div class="ash-grid"> 28 <label> Datum<input type="date" name="date" required></label>29 <label> Tijd (optioneel)<input type="time" name="time"></label>30 <label> Plaats<input type="text" name="city" required placeholder="Amsterdam"></label>31 <label> Land (optioneel)<input type="text" name="country" placeholder="NL"></label>32 <label class="ash-wide"> Locatie/zaal (optioneel)<input type="text" name="venue" placeholder="Paradiso"></label>33 <label class="ash-wide"> Ticket-URL (optioneel)<input type="url" name="ticket_url" placeholder="https://..."></label>34 <label class="ash-wide"> Notitie (optioneel)<input type="text" name="notes" placeholder="Support: ..."></label>28 <label><%= t('ashow.f_date') %><input type="date" name="date" required></label> 29 <label><%= t('ashow.f_time') %><input type="time" name="time"></label> 30 <label><%= t('ashow.f_city') %><input type="text" name="city" required placeholder="Amsterdam"></label> 31 <label><%= t('ashow.f_country') %><input type="text" name="country" placeholder="NL"></label> 32 <label class="ash-wide"><%= t('ashow.f_venue') %><input type="text" name="venue" placeholder="Paradiso"></label> 33 <label class="ash-wide"><%= t('ashow.f_ticket') %><input type="url" name="ticket_url" placeholder="https://..."></label> 34 <label class="ash-wide"><%= t('ashow.f_notes') %><input type="text" name="notes" placeholder="<%= t('ashow.f_notes_ph') %>"></label> 35 35 </div> 36 <label class="ash-check"><input type="checkbox" name="notify" value="1" <%= smtp ? '' : 'disabled' %>> Abonnees per e-mail op de hoogte brengen<%= smtp ? '' : ' (SMTP vereist)'%></label>37 <button type="submit" class="ash-add"> + Evenement toevoegen</button>36 <label class="ash-check"><input type="checkbox" name="notify" value="1" <%= smtp ? '' : 'disabled' %>> <%= t('ashow.notify_label') %><%= smtp ? '' : ' ' + t('ashow.smtp_required') %></label> 37 <button type="submit" class="ash-add"><%= t('ashow.add_event') %></button> 38 38 </form> 39 39 40 40 <% if (ss.length) { %> 41 <% var _delConfirm = t('ashow.del_confirm'); %> 41 42 <ul class="ash-list"> 42 43 <% ss.forEach(function(s){ %> … … 44 45 <span class="ash-when"><%= s.date %><% if (s.time) { %> <%= s.time %><% } %></span> 45 46 <span class="ash-where"><%= s.city %><% if (s.venue) { %> — <%= s.venue %><% } %></span> 46 <form method="POST" action="<%= siteUrlBase %>/admin/shows/<%= s.id %>/delete" onsubmit="return confirm(' Evenement verwijderen??')" style="margin:0">47 <form method="POST" action="<%= siteUrlBase %>/admin/shows/<%= s.id %>/delete" onsubmit="return confirm('<%= _delConfirm %>')" style="margin:0"> 47 48 <button type="submit" class="ash-del">🗑</button> 48 49 </form> … … 51 52 </ul> 52 53 <% } else { %> 53 <p class="ash-empty"> Nog geen evenementen.</p>54 <p class="ash-empty"><%= t('ashow.empty') %></p> 54 55 <% } %> 55 56 </section> -
src/views/pages/admin-site-edit.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-site-edit-page"> 2 <p><a href="/admin" class="btn">← Beheer</a></p>3 <h1><%= isNew ? 'Nieuwe site' : 'Uiterlijk: ' + site.title %></h1>2 <p><a href="/admin" class="btn">← <%= t('asite.back_admin') %></a></p> 3 <h1><%= isNew ? t('asite.title_new') : t('asite.title_edit') + ': ' + site.title %></h1> 4 4 5 5 <% if (typeof success !== 'undefined' && success) { %><div class="audio-flash audio-flash--ok"><%= success %></div><% } %> … … 9 9 10 10 <fieldset> 11 <legend> Identiteit</legend>12 <label> 13 <span> Slug (URL) <% if (!isNew) { %><em>(vast)</em><% } %></span>11 <legend><%= t('asite.identity') %></legend> 12 <label> 13 <span><%= t('asite.slug') %> <% if (!isNew) { %><em><%= t('asite.slug_fixed') %></em><% } %></span> 14 14 <span class="slug-url"> 15 15 <span class="slug-url-host" id="slug-host" 16 16 data-prefix="<%= (typeof tenancy !== 'undefined' && tenancy === 'hub') ? '/user/' : '/' %>">website.com/</span><input 17 17 type="text" name="slug" id="slug-input" class="slug-url-input" value="<%= site.slug %>" required <% if (!isNew) { %>readonly<% } %> 18 pattern="[a-z0-9_-]{2,40}" placeholder=" jouwslug"18 pattern="[a-z0-9_-]{2,40}" placeholder="<%= t('asite.slug_placeholder') %>" 19 19 autocapitalize="none" autocorrect="off" spellcheck="false"> 20 20 </span> 21 21 </label> 22 22 <label> 23 <span> Titel <small class="form-hint-inline">— getoond in de kop en als weergavenaam</small></span>23 <span><%= t('asite.field_title') %> <small class="form-hint-inline"><%= t('asite.field_title_hint') %></small></span> 24 24 <input type="text" name="title" value="<%= site.title || '' %>" required maxlength="200"> 25 25 </label> … … 28 28 <% if (user && user.role === 'god') { %> 29 29 <label> 30 <span>Eigenaar <small class="form-hint-inline">— wie deze Klonkt zelf mag beheren</small></span> 30 <span><%= t('asite.owner') %> <small class="form-hint-inline"><%= t('asite.owner_hint') %></small></span> 31 <% var _godSuffix = t('asite.owner_god_suffix'); %> 31 32 <select name="owner_id"> 32 33 <% (typeof users !== 'undefined' ? users : []).forEach(function(u){ %> 33 <option value="<%= u.id %>" <%= (site.owner_id === u.id) ? 'selected' : '' %>><%= u.username %><%= u.role === 'god' ? ' (god)': '' %></option>34 <option value="<%= u.id %>" <%= (site.owner_id === u.id) ? 'selected' : '' %>><%= u.username %><%= u.role === 'god' ? _godSuffix : '' %></option> 34 35 <% }); %> 35 36 </select> … … 37 38 <% } %> 38 39 <label> 39 <span> Tagline <small class="form-hint-inline">— korte oneliner</small></span>40 <span><%= t('asite.tagline') %> <small class="form-hint-inline"><%= t('asite.tagline_hint') %></small></span> 40 41 <input type="text" name="tagline" value="<%= site.tagline || '' %>" maxlength="200"> 41 42 </label> 42 43 <label> 43 <span> Bio / omschrijving <small class="form-hint-inline">— getoond in de profielkop en gebruikt voor SEO</small></span>44 <span><%= t('asite.bio') %> <small class="form-hint-inline"><%= t('asite.bio_hint') %></small></span> 44 45 <textarea name="description" rows="3" maxlength="500"><%= site.description || '' %></textarea> 45 46 </label> 46 47 <label> 47 <span> Profielfoto</span>48 <span><%= t('asite.profile_photo') %></span> 48 49 <div class="photo-picker" id="photo-picker"> 49 50 <div class="photo-thumb" id="photo-thumb" <%= site.profile_photo ? '' : 'data-empty' %>> … … 54 55 </div> 55 56 <div class="photo-controls"> 56 <input type="text" name="profile_photo" id="photo-url" value="<%= site.profile_photo || '' %>" placeholder=" /media/avatars/foo.jpg of https://…">57 <input type="text" name="profile_photo" id="photo-url" value="<%= site.profile_photo || '' %>" placeholder="<%= t('asite.photo_url_placeholder') %>"> 57 58 <div class="photo-actions"> 58 <button type="button" class="btn" id="photo-upload-trigger"> 📷 Uploaden</button>59 <button type="button" class="btn" id="photo-clear" <%= site.profile_photo ? '' : 'hidden' %>> Verwijder</button>59 <button type="button" class="btn" id="photo-upload-trigger"><%= t('asite.photo_upload') %></button> 60 <button type="button" class="btn" id="photo-clear" <%= site.profile_photo ? '' : 'hidden' %>><%= t('asite.photo_clear') %></button> 60 61 <small id="photo-status" class="form-hint photo-status"></small> 61 62 </div> … … 65 66 </label> 66 67 <label> 67 <span> Taal (ISO-code)</span>68 <span><%= t('asite.language') %></span> 68 69 <input type="text" name="language" value="<%= site.language || 'nl' %>" maxlength="8"> 69 70 </label> 70 71 <label class="cb"> 71 72 <input type="checkbox" name="profile_enabled" value="1" <%= site.profile_enabled ? 'checked' : '' %>> 72 <span> Profielkop tonen onder de navigatie</span>73 <span><%= t('asite.profile_enabled') %></span> 73 74 </label> 74 75 </fieldset> 75 76 76 77 <fieldset> 77 <legend> Vormgeving</legend>78 <legend><%= t('asite.appearance') %></legend> 78 79 79 80 <%# ── Curated accent picker ─────────────────────────────────────── … … 82 83 still what's stored in the DB — we just constrain WHICH hex. %> 83 84 <fieldset class="accent-grid"> 84 <legend> Accentkleur</legend>85 <legend><%= t('asite.accent_color') %></legend> 85 86 <% const _curAccent = (site.accent || '#c2410c').toLowerCase(); %> 86 87 <% accents.forEach(function(a) { %> … … 99 100 visitors with a localStorage override, still win over this default. %> 100 101 <fieldset class="theme-default"> 101 <legend> Standaardthema voor nieuwe bezoekers</legend>102 <legend><%= t('asite.theme_default') %></legend> 102 103 <% const td = site.theme_override || ''; %> 103 104 <label class="cb"> 104 105 <input type="radio" name="theme_override" value="" <%= td === '' ? 'checked' : '' %>> 105 <span> Auto (volg device-voorkeur)</span>106 <span><%= t('asite.theme_auto') %></span> 106 107 </label> 107 108 <label class="cb"> 108 109 <input type="radio" name="theme_override" value="light" <%= td === 'light' ? 'checked' : '' %>> 109 <span> Licht</span>110 <span><%= t('asite.theme_light') %></span> 110 111 </label> 111 112 <label class="cb"> 112 113 <input type="radio" name="theme_override" value="dark" <%= td === 'dark' ? 'checked' : '' %>> 113 <span> Donker</span>114 <span><%= t('asite.theme_dark') %></span> 114 115 </label> 115 116 </fieldset> 116 117 117 118 <fieldset class="palette-grid"> 118 <legend> Palet</legend>119 <legend><%= t('asite.palette') %></legend> 119 120 <% palettes.forEach(function(p) { %> 120 121 <label class="palette-swatch"> … … 137 138 138 139 <fieldset> 139 <legend> Gedrag</legend>140 <legend><%= t('asite.behavior') %></legend> 140 141 <label class="cb"> 141 142 <input type="checkbox" name="is_public" value="1" <%= site.is_public ? 'checked' : '' %>> 142 <span> Publieke site (uitvinken voor een besloten kring)</span>143 <span><%= t('asite.is_public') %></span> 143 144 </label> 144 145 <label class="cb"> 145 146 <input type="checkbox" name="robots_index" value="1" <%= site.robots_index ? 'checked' : '' %>> 146 <span> Zoekmachines mogen indexeren (sitemap.xml is verborgen indien uit)</span>147 <span><%= t('asite.robots_index') %></span> 147 148 </label> 148 149 <label class="cb"> 149 150 <input type="checkbox" name="require_login_to_comment" value="1" <%= site.require_login_to_comment ? 'checked' : '' %>> 150 <span> Inloggen vereist om te reageren</span>151 </label> 152 <label> 153 <span> Reactie-moderatie</span>151 <span><%= t('asite.require_login_comment') %></span> 152 </label> 153 <label> 154 <span><%= t('asite.comment_moderation') %></span> 154 155 <select name="comments_moderation_mode"> 155 <option value="trust" <%= (site.comments_moderation_mode || 'trust') === 'trust' ? 'selected' : '' %>> Vertrouwen (automatisch goedkeuren)</option>156 <option value="moderate" <%= site.comments_moderation_mode === 'moderate' ? 'selected' : '' %>> Modereren (eerst in de wachtrij)</option>156 <option value="trust" <%= (site.comments_moderation_mode || 'trust') === 'trust' ? 'selected' : '' %>><%= t('asite.moderation_trust') %></option> 157 <option value="moderate" <%= site.comments_moderation_mode === 'moderate' ? 'selected' : '' %>><%= t('asite.moderation_moderate') %></option> 157 158 </select> 158 159 </label> 159 160 <label class="cb"> 160 161 <input type="checkbox" name="enable_audio_player" value="1" <%= site.enable_audio_player ? 'checked' : '' %>> 161 <span> Audiospeler + embeds inschakelen</span>162 <span><%= t('asite.enable_audio') %></span> 162 163 </label> 163 164 <%# Prutter is een Hub-only premium-functie — toggle alleen tonen in Hub-modus. %> … … 165 166 <label class="cb"> 166 167 <input type="checkbox" name="enable_prutter" value="1" <%= site.enable_prutter ? 'checked' : '' %>> 167 <span> Prutter (DM’s tussen leden) inschakelen <small class="form-hint-inline">— Hub-functie, premium</small></span>168 <span><%= t('asite.enable_prutter') %> <small class="form-hint-inline"><%= t('asite.enable_prutter_hint') %></small></span> 168 169 </label> 169 170 <% } %> … … 171 172 172 173 <fieldset class="profile-links-fieldset"> 173 <legend> Social / streaming-links</legend>174 <p class="form-hint"> Getoond als merk-iconen op de profielkop. Voeg er zoveel toe als je wilt.</p>174 <legend><%= t('asite.links') %></legend> 175 <p class="form-hint"><%= t('asite.links_hint') %></p> 175 176 176 177 <div id="profile-links-rows"> … … 200 201 </template> 201 202 202 <button type="button" id="profile-link-add" class="btn"> + Link toevoegen</button>203 <button type="button" id="profile-link-add" class="btn"><%= t('asite.link_add') %></button> 203 204 </fieldset> 204 205 205 206 <fieldset> 206 <legend> Feed-weergave</legend>207 <label> 208 <span> Standaardweergave voor de homepage</span>207 <legend><%= t('asite.feed_view') %></legend> 208 <label> 209 <span><%= t('asite.feed_default') %></span> 209 210 <select name="feed_view_default"> 210 <option value="timeline" <%= (site.feed_view_default || 'timeline') === 'timeline' ? 'selected' : '' %>> Tijdlijn (chronologische lijst)</option>211 <option value="grid" <%= site.feed_view_default === 'grid' ? 'selected' : '' %>> Grid (kaarten)</option>211 <option value="timeline" <%= (site.feed_view_default || 'timeline') === 'timeline' ? 'selected' : '' %>><%= t('asite.feed_timeline') %></option> 212 <option value="grid" <%= site.feed_view_default === 'grid' ? 'selected' : '' %>><%= t('asite.feed_grid') %></option> 212 213 </select> 213 214 </label> 214 215 <label class="cb"> 215 216 <input type="checkbox" name="feed_view_switch" value="1" <%= site.feed_view_switch ? 'checked' : '' %>> 216 <span> Tijdlijn ↔ grid-schakelaar boven de feed tonen</span>217 <span><%= t('asite.feed_switch') %></span> 217 218 </label> 218 219 <label class="cb"> 219 220 <input type="checkbox" name="show_search" value="1" <%= site.show_search ? 'checked' : '' %>> 220 <span> Zoekknop in de navigatie tonen</span>221 <span><%= t('asite.show_search') %></span> 221 222 </label> 222 223 <label class="cb"> 223 224 <input type="checkbox" name="show_archive_link" value="1" <%= site.show_archive_link ? 'checked' : '' %>> 224 <span> Archief-link in de navigatie tonen</span>225 <span><%= t('asite.show_archive') %></span> 225 226 </label> 226 227 </fieldset> 227 228 228 229 <fieldset class="seo-pointer"> 229 <legend>SEO & social</legend> 230 <p>Titel-sjabloon, canonical, deel-afbeelding, verificatie-metas en meer staan 231 nu op een eigen pagina: <a href="/admin/seo">🔎 SEO & vindbaarheid</a>.</p> 230 <legend><%= t('asite.seo') %></legend> 231 <p><%= t('asite.seo_pointer') %> <a href="/admin/seo"><%= t('asite.seo_link') %></a>.</p> 232 232 </fieldset> 233 233 234 234 <fieldset> 235 <legend> Eigen CSS & HTML <em>(optioneel)</em></legend>236 <label> 237 <span> Eigen CSS (geïnjecteerd als <style> in <head>)</span>235 <legend><%= t('asite.custom_legend') %> <em><%= t('asite.optional') %></em></legend> 236 <label> 237 <span><%= t('asite.custom_css') %></span> 238 238 <textarea name="custom_css" rows="3" placeholder=".foo { color: red; }"><%= site.custom_css || '' %></textarea> 239 239 </label> 240 240 <label> 241 <span> Eigen <head>-HTML (analytics, extra meta’s)</span>241 <span><%= t('asite.custom_head') %></span> 242 242 <textarea name="custom_head_html" rows="3" placeholder="<meta name="...">"><%= site.custom_head_html || '' %></textarea> 243 243 </label> 244 244 <label> 245 <span> Eigen footer-HTML</span>245 <span><%= t('asite.custom_foot') %></span> 246 246 <textarea name="custom_foot_html" rows="3"><%= site.custom_foot_html || '' %></textarea> 247 247 </label> 248 248 </fieldset> 249 249 250 <button type="submit" class="btn btn-primary"><%= isNew ? 'Site aanmaken' : 'Wijzigingen opslaan'%></button>250 <button type="submit" class="btn btn-primary"><%= isNew ? t('asite.submit_create') : t('asite.submit_save') %></button> 251 251 </form> 252 252 </div> -
src/views/pages/admin-sites.ejs
r929d98d rbbf9d1a 1 1 <div class="container ax-page"> 2 2 3 <p><a href="/admin" class="btn">← Beheer</a></p>3 <p><a href="/admin" class="btn">← <%= t('asit2.back') %></a></p> 4 4 <header class="ax-header"> 5 5 <div> 6 <h1> Sites</h1>7 <p class="ax-tagline"> Beheer alle sites op deze installatie.</p>6 <h1><%= t('asit2.title') %></h1> 7 <p class="ax-tagline"><%= t('asit2.tagline') %></p> 8 8 </div> 9 9 </header> … … 13 13 14 14 <div class="ax-actions-bar"> 15 <a href="/admin/sites/new" class="ax-btn ax-btn-primary">+ Nieuwe site</a>15 <a href="/admin/sites/new" class="ax-btn ax-btn-primary">+ <%= t('asit2.new_site') %></a> 16 16 </div> 17 17 … … 20 20 <div class="ax-empty"> 21 21 <div class="ax-empty-icon">🌐</div> 22 <p> Nog geen sites.</p>23 <p class="ax-empty-sub"> Maak er een via de knop hierboven.</p>22 <p><%= t('asit2.empty') %></p> 23 <p class="ax-empty-sub"><%= t('asit2.empty_sub') %></p> 24 24 </div> 25 25 </div> 26 26 <% } else { %> 27 <% 28 var _s_primary = t('asit2.pill_primary'); 29 var _s_primary_t = t('asit2.pill_primary_title'); 30 var _s_public = t('asit2.pill_public'); 31 var _s_public_t = t('asit2.pill_public_title'); 32 var _s_private = t('asit2.pill_private'); 33 var _s_private_t = t('asit2.pill_private_title'); 34 var _s_noindex = t('asit2.pill_noindex'); 35 var _s_noindex_t = t('asit2.pill_noindex_title'); 36 var _s_by = t('asit2.by'); 37 var _s_t_posts = t('asit2.t_posts'); 38 var _s_l_posts = t('asit2.l_posts'); 39 var _s_t_created = t('asit2.t_created'); 40 var _s_l_created = t('asit2.l_created'); 41 var _s_makeprim = t('asit2.make_primary'); 42 var _s_makeprim_t = t('asit2.make_primary_title'); 43 var _s_makeprim_c = t('asit2.make_primary_confirm'); 44 var _s_edit = t('asit2.edit'); 45 var _s_del = t('asit2.delete'); 46 var _s_del_c = t('asit2.delete_confirm'); 47 %> 27 48 <ul class="ax-list"> 28 49 <% sites.forEach(function(s) { %> … … 37 58 <a href="/user/<%= s.slug %>"><%= s.title %></a> 38 59 <% if (s.is_primary) { %> 39 <span class="ax-pill ax-pill-primary" title=" Hoofd-/labelsite van deze installatie">★ primair</span>60 <span class="ax-pill ax-pill-primary" title="<%= _s_primary_t %>">★ <%= _s_primary %></span> 40 61 <% } %> 41 62 <% if (s.is_public) { %> 42 <span class="ax-pill ax-pill-ok" title=" Publiek zichtbaar">✓ public</span>63 <span class="ax-pill ax-pill-ok" title="<%= _s_public_t %>">✓ <%= _s_public %></span> 43 64 <% } else { %> 44 <span class="ax-pill" title=" Niet publiek">🔒 private</span>65 <span class="ax-pill" title="<%= _s_private_t %>">🔒 <%= _s_private %></span> 45 66 <% } %> 46 67 <% if (!s.robots_index) { %> 47 <span class="ax-pill" title=" Niet geïndexeerd door zoekmachines">🚫🤖 noindex</span>68 <span class="ax-pill" title="<%= _s_noindex_t %>">🚫🤖 <%= _s_noindex %></span> 48 69 <% } %> 49 70 </div> … … 52 73 <% if (s.owner_username) { %> 53 74 <span class="ax-track-sep">·</span> 54 <span> door<%= s.owner_username %></span>75 <span><%= _s_by %> <%= s.owner_username %></span> 55 76 <% } %> 56 77 </div> … … 59 80 60 81 <div class="ax-site-stats"> 61 <span class="ax-stat" title=" Aantal posts">82 <span class="ax-stat" title="<%= _s_t_posts %>"> 62 83 <span class="ax-stat-num"><%= s.post_count %></span> 63 <span class="ax-stat-label"> posts</span>84 <span class="ax-stat-label"><%= _s_l_posts %></span> 64 85 </span> 65 <span class="ax-stat" title=" Aangemaakt op">86 <span class="ax-stat" title="<%= _s_t_created %>"> 66 87 <span class="ax-stat-num ax-stat-date"><%= formatDate(s.created_at) %></span> 67 <span class="ax-stat-label"> created</span>88 <span class="ax-stat-label"><%= _s_l_created %></span> 68 89 </span> 69 90 </div> … … 72 93 <% if (!s.is_primary && (typeof canMutate === 'undefined' || canMutate)) { %> 73 94 <form method="post" action="/admin/sites/<%= s.slug %>/make-primary" style="margin:0;display:inline" 74 onsubmit="return confirm(' Deze site instellen als de primaire/hoofd-site?')">75 <button type="submit" class="ax-icon-btn" aria-label=" Maak primair" title="Maak primaire/hoofd-site">★</button>95 onsubmit="return confirm('<%= _s_makeprim_c %>')"> 96 <button type="submit" class="ax-icon-btn" aria-label="<%= _s_makeprim %>" title="<%= _s_makeprim_t %>">★</button> 76 97 </form> 77 98 <% } %> 78 <a href="/admin/sites/<%= s.slug %>/edit" class="ax-icon-btn" aria-label=" Bewerken" title="Bewerken">✎</a>99 <a href="/admin/sites/<%= s.slug %>/edit" class="ax-icon-btn" aria-label="<%= _s_edit %>" title="<%= _s_edit %>">✎</a> 79 100 <form method="post" action="/admin/sites/<%= s.slug %>/delete" class="ax-delete-form" 80 onsubmit="return confirm(' Site verwijderen? Lukt alleen als er geen posts zijn.')">81 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label=" Verwijderen" title="Verwijderen">🗑</button>101 onsubmit="return confirm('<%= _s_del_c %>')"> 102 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _s_del %>" title="<%= _s_del %>">🗑</button> 82 103 </form> 83 104 </div> -
src/views/pages/admin-stats.ejs
r929d98d rbbf9d1a 1 1 <div class="container stats-page"> 2 2 <p><a href="/admin" class="btn">← Beheer</a></p> 3 <h1>Statistieken</h1> 4 <p class="stats-note">Cookievrij gemeten — geen tracking-cookies, geen toestemmingsbanner. 5 Bezoekers worden per dag geteld via een dagelijks roterende, anonieme hash (IP/browser worden 6 niet bewaard). Je eigen beheerder-bezoeken en bekende bots/crawlers tellen niet mee.</p> 3 <h1><%= t('astat.title') %></h1> 4 <p class="stats-note"><%= t('astat.intro') %></p> 7 5 8 6 <% var s = stats; var _max = Math.max(1, ...s.series.map(function(d){ return d.pageviews; })); %> 9 7 10 8 <div class="stats-cards"> 11 <div class="stat-card"><div class="stat-num"><%= s.totals.visitors %></div><div class="stat-label"> Bezoeker-dagen (<%= s.days %>d)</div></div>12 <div class="stat-card"><div class="stat-num"><%= s.totals.pageviews %></div><div class="stat-label"> Weergaven (<%= s.days %>d)</div></div>13 <div class="stat-card"><div class="stat-num"><%= s.totals.plays %></div><div class="stat-label"> Plays (totaal)</div></div>14 <div class="stat-card"><div class="stat-num"><%= s.totals.postViews %></div><div class="stat-label"> Post-weergaven (totaal)</div></div>9 <div class="stat-card"><div class="stat-num"><%= s.totals.visitors %></div><div class="stat-label"><%= t('astat.visitor_days', { n: s.days }) %></div></div> 10 <div class="stat-card"><div class="stat-num"><%= s.totals.pageviews %></div><div class="stat-label"><%= t('astat.pageviews_days', { n: s.days }) %></div></div> 11 <div class="stat-card"><div class="stat-num"><%= s.totals.plays %></div><div class="stat-label"><%= t('astat.plays_total') %></div></div> 12 <div class="stat-card"><div class="stat-num"><%= s.totals.postViews %></div><div class="stat-label"><%= t('astat.postviews_total') %></div></div> 15 13 </div> 16 14 17 15 <% if (typeof s.allTime !== 'undefined') { %> 18 <p class="stats-note"> All-time: <strong><%= s.allTime.pageviews %></strong> weergaven · <strong><%= s.allTime.visitorDays %></strong> bezoeker-dagen.</p>16 <p class="stats-note"><%= t('astat.alltime_pre') %> <strong><%= s.allTime.pageviews %></strong> <%= t('astat.alltime_mid') %> · <strong><%= s.allTime.visitorDays %></strong> <%= t('astat.alltime_post') %></p> 19 17 <% } %> 20 18 21 19 <details class="stats-help"> 22 <summary> Wat betekenen deze cijfers?</summary>20 <summary><%= t('astat.help_summary') %></summary> 23 21 <ul> 24 <li><strong> Bezoeker-dagen</strong> — het aantal unieke bezoekers <em>per dag, bij elkaar opgeteld</em>. Eén persoon die 5 dagen langskomt = 5 bezoeker-dagen. Cookieloos kan er niet over dagen heen geteld worden, dus dit is géén aantal unieke personen — het echte aantal mensen ligt (vaak fors) lager.</li>25 <li><strong> Weergaven</strong> — hoe vaak de home/feed of een post geladen is (ook bij klikken binnen de site). Andere pagina's (agenda, downloads, links) tellen hier niet in mee.</li>26 <li><strong> Plays</strong> — totaal aantal keren dat een track is gestart.</li>27 <li><strong> Post-weergaven</strong> — totaal over alle posts samen.</li>22 <li><strong><%= t('astat.help_vd_term') %></strong> — <%= t('astat.help_vd_a') %> <em><%= t('astat.help_vd_em') %></em><%= t('astat.help_vd_b') %></li> 23 <li><strong><%= t('astat.help_pv_term') %></strong> — <%= t('astat.help_pv') %></li> 24 <li><strong><%= t('astat.help_plays_term') %></strong> — <%= t('astat.help_plays') %></li> 25 <li><strong><%= t('astat.help_postviews_term') %></strong> — <%= t('astat.help_postviews') %></li> 28 26 </ul> 29 <p> Beheerder-bezoeken en bekende bots/crawlers worden overgeslagen. Het ruwe IP wordt nooit bewaard. Goed voor trends; neem absolute aantallen met een korrel zout.</p>27 <p><%= t('astat.help_footer') %></p> 30 28 </details> 31 29 32 30 <div class="stats-period"> 33 Periode:31 <%= t('astat.period') %> 34 32 <% [7,14,30,90].forEach(function(n){ %> 35 33 <a href="/admin/stats?days=<%= n %>" class="<%= s.days === n ? 'is-on' : '' %>"><%= n %>d</a> … … 39 37 <section class="stats-chart-wrap"> 40 38 <div class="stats-chart-head"> 41 <h2> Laatste <%= s.days %> dagen</h2>39 <h2><%= t('astat.last_n_days', { n: s.days }) %></h2> 42 40 <div class="stats-legend"> 43 <span class="lg lg-vis"> Bezoeker-dagen</span>44 <span class="lg lg-pv"> Weergaven</span>41 <span class="lg lg-vis"><%= t('astat.lg_visitor_days') %></span> 42 <span class="lg lg-pv"><%= t('astat.lg_pageviews') %></span> 45 43 </div> 46 44 </div> … … 51 49 var visPct = Math.round(d.visitors / _max * 100); 52 50 %> 53 <div class="sc-col" title="<%= d.day %> — <%= d.pageviews %> weergaven, <%= d.visitors %> bezoeker-dagen">51 <div class="sc-col" title="<%= t('astat.bar_title', { day: d.day, pv: d.pageviews, vd: d.visitors }) %>"> 54 52 <div class="sc-bar"> 55 53 <span class="sc-pv" style="height: <%= pvPct %>%"></span> … … 64 62 <div class="stats-tops"> 65 63 <section class="stats-top"> 66 <h2> Populairste posts</h2>64 <h2><%= t('astat.top_posts') %></h2> 67 65 <% if (!s.topPosts.length || s.topPosts[0].views === 0) { %> 68 <p class="stats-empty"> Nog geen weergaven.</p>66 <p class="stats-empty"><%= t('astat.no_views') %></p> 69 67 <% } else { %> 70 68 <ol> … … 76 74 </section> 77 75 <section class="stats-top"> 78 <h2> Meest geluisterd</h2>76 <h2><%= t('astat.most_played') %></h2> 79 77 <% if (!s.topTracks.length || s.topTracks[0].plays === 0) { %> 80 <p class="stats-empty"> Nog geen plays.</p>78 <p class="stats-empty"><%= t('astat.no_plays') %></p> 81 79 <% } else { %> 82 80 <ol> … … 91 89 <% if (typeof s.referrers !== 'undefined' && s.referrers.length) { %> 92 90 <section class="stats-top" style="margin-top:1.2rem"> 93 <h2>🌐 Bronnen (waar bezoekers vandaan komen)</h2>91 <h2>🌐 <%= t('astat.sources') %></h2> 94 92 <ol> 95 93 <% s.referrers.forEach(function(r){ %> … … 102 100 <% if (typeof linkClicks !== 'undefined' && linkClicks.length) { %> 103 101 <section class="stats-top" style="margin-top:1.2rem"> 104 <h2>🔗 Link-in-bio klikken</h2>102 <h2>🔗 <%= t('astat.linkbio_clicks') %></h2> 105 103 <ol> 106 104 <% linkClicks.forEach(function(l){ %> -
src/views/pages/admin-updates.ejs
r929d98d rbbf9d1a 1 1 <div class="container admin-page"> 2 <h1> Updates</h1>3 <p><a href="/admin" class="btn">← Beheer</a></p>2 <h1><%= t('aupd.title') %></h1> 3 <p><a href="/admin" class="btn">← <%= t('aupd.back_admin') %></a></p> 4 4 5 5 <% if (success) { %><div class="alert alert-success"><%= success %></div><% } %> … … 7 7 8 8 <section class="set-card"> 9 <h2> Versie van deze Klonkt</h2>9 <h2><%= t('aupd.version_heading') %></h2> 10 10 11 11 <div class="upd-row"> 12 <span class="upd-label"> App-versie</span>12 <span class="upd-label"><%= t('aupd.app_version') %></span> 13 13 <span class="upd-val"><%= appVersion || '—' %></span> 14 14 </div> 15 15 <div class="upd-row"> 16 <span class="upd-label"> Huidig</span>16 <span class="upd-label"><%= t('aupd.current') %></span> 17 17 <span class="upd-val"> 18 18 <% if (currentSha) { %><code><%= currentSha %></code> <span class="upd-desc"><%= currentDesc %></span> 19 <% } else { %><em> onbekend (nog niet via de update-knop bijgewerkt)</em><% } %>19 <% } else { %><em><%= t('aupd.current_unknown') %></em><% } %> 20 20 </span> 21 21 </div> 22 22 <div class="upd-row"> 23 <span class="upd-label"> Nieuwste</span>23 <span class="upd-label"><%= t('aupd.latest') %></span> 24 24 <span class="upd-val"> 25 25 <% if (latestSha) { %><code><%= latestSha %></code> <span class="upd-desc"><%= latestDesc %></span> 26 <% } else { %><em> kon de nieuwste versie niet ophalen</em><% } %>26 <% } else { %><em><%= t('aupd.latest_failed') %></em><% } %> 27 27 </span> 28 28 </div> … … 30 30 <div class="upd-status"> 31 31 <% if (!canCheck) { %> 32 <span class="upd-badge warn">⚠ Geen update-bron bereikbaar</span>32 <span class="upd-badge warn">⚠ <%= t('aupd.no_source') %></span> 33 33 <% } else if (upToDate) { %> 34 <span class="upd-badge ok">✓ Up-to-date</span>34 <span class="upd-badge ok">✓ <%= t('aupd.uptodate') %></span> 35 35 <% } else { %> 36 <span class="upd-badge new">↻ Update beschikbaar<% if (behind) { %> (<%= behind %> commit<%= behind === '1' ? '' : 's' %> achter)<% } %></span>36 <span class="upd-badge new">↻ <%= t('aupd.update_available') %><% if (behind) { %> (<%= behind === '1' ? t('aupd.behind_one', { n: behind }) : t('aupd.behind_many', { n: behind }) %>)<% } %></span> 37 37 <% } %> 38 38 </div> … … 40 40 <% if (canCheck) { %> 41 41 <form method="post" action="/admin/updates/run" class="set-form" 42 onsubmit="return confirm(' De site wordt naar de nieuwste versie gebracht en herstart kort. Doorgaan?');">42 onsubmit="return confirm('<%= t('aupd.run_confirm') %>');"> 43 43 <button type="submit" class="btn btn-primary"> 44 <%= upToDate ? '↻ Opnieuw uitrollen' : '⬆ Nu bijwerken'%>44 <%= upToDate ? '↻ ' + t('aupd.redeploy') : '⬆ ' + t('aupd.update_now') %> 45 45 </button> 46 46 </form> … … 48 48 49 49 <p class="set-help" style="margin-top:1rem"> 50 Bijwerken haalt de nieuwste code op en herstart deze site kort (~10s). Doe dit 51 rustig — er gaat niets verloren (je posts, instellingen en cirkel blijven staan). 50 <%= t('aupd.help') %> 52 51 </p> 53 52 </section> -
src/views/pages/admin-users.ejs
r929d98d rbbf9d1a 1 1 <div class="container ax-page"> 2 2 3 <p><a href="/admin" class="btn">← Beheer</a></p>3 <p><a href="/admin" class="btn">← <%= t('ausr.back') %></a></p> 4 4 <header class="ax-header"> 5 5 <div> 6 <h1> Users</h1>7 <p class="ax-tagline"> Beheer gebruikers, rollen, en verwijderingen. Rol <strong>kijker</strong> = alles bekijken (incl. Beheer), niets wijzigen — handig voor demo's.</p>6 <h1><%= t('ausr.title') %></h1> 7 <p class="ax-tagline"><%= t('ausr.tagline_a') %> <strong><%= t('ausr.role_kijker') %></strong> <%= t('ausr.tagline_b') %></p> 8 8 </div> 9 9 </header> … … 16 16 <div class="ax-empty"> 17 17 <div class="ax-empty-icon">👤</div> 18 <p> Geen gebruikers.</p>18 <p><%= t('ausr.empty') %></p> 19 19 </div> 20 20 </div> 21 21 <% } else { %> 22 <% 23 var _u_self = t('ausr.you'); 24 var _u_t_sites = t('ausr.t_sites'); 25 var _u_t_posts = t('ausr.t_posts'); 26 var _u_t_joined = t('ausr.t_joined'); 27 var _u_l_sites = t('ausr.l_sites'); 28 var _u_l_posts = t('ausr.l_posts'); 29 var _u_l_joined = t('ausr.l_joined'); 30 var _u_newklonkt = t('ausr.new_klonkt'); 31 var _u_rol = t('ausr.role_label'); 32 var _u_r_kijker = t('ausr.role_kijker'); 33 var _u_r_member = t('ausr.role_member'); 34 var _u_r_admin = t('ausr.role_admin'); 35 var _u_r_god = t('ausr.role_god'); 36 var _u_del = t('ausr.delete'); 37 %> 22 38 <ul class="ax-list"> 23 39 <% users.forEach(function(u) { %> … … 33 49 <div class="ax-user-name"> 34 50 <a href="/users/<%= encodeURIComponent(u.username) %>"><%= u.username %></a> 35 <% if (u.id === user.id) { %><span class="ax-pill"> jij</span><% } %>51 <% if (u.id === user.id) { %><span class="ax-pill"><%= _u_self %></span><% } %> 36 52 </div> 37 53 <div class="ax-user-email"><%= u.email %></div> … … 40 56 41 57 <div class="ax-user-stats"> 42 <span class="ax-stat" title=" Sites">58 <span class="ax-stat" title="<%= _u_t_sites %>"> 43 59 <span class="ax-stat-num"><%= u.site_count %></span> 44 <span class="ax-stat-label"> sites</span>60 <span class="ax-stat-label"><%= _u_l_sites %></span> 45 61 </span> 46 <span class="ax-stat" title=" Posts">62 <span class="ax-stat" title="<%= _u_t_posts %>"> 47 63 <span class="ax-stat-num"><%= u.post_count %></span> 48 <span class="ax-stat-label"> posts</span>64 <span class="ax-stat-label"><%= _u_l_posts %></span> 49 65 </span> 50 <span class="ax-stat" title=" Geregistreerd op">66 <span class="ax-stat" title="<%= _u_t_joined %>"> 51 67 <span class="ax-stat-num ax-stat-date"><%= formatDate(u.created_at) %></span> 52 <span class="ax-stat-label"> joined</span>68 <span class="ax-stat-label"><%= _u_l_joined %></span> 53 69 </span> 54 70 </div> … … 59 75 <% if (typeof tenancy !== 'undefined' && tenancy === 'hub' && canMutate) { %> 60 76 <a href="/admin/sites/new?owner=<%= u.id %>" class="ax-icon-btn" 61 aria-label=" Nieuwe Klonkt voor <%= u.username %>" title="Nieuwe Klonkt voor deze gebruiker">+🌐</a>77 aria-label="<%= t('ausr.new_klonkt_for', { name: u.username }) %>" title="<%= _u_newklonkt %>">+🌐</a> 62 78 <% } %> 63 79 <form method="post" action="/admin/users/<%= u.id %>/role" class="ax-role-form"> 64 80 <label class="ax-role"> 65 <span> Rol</span>81 <span><%= _u_rol %></span> 66 82 <select name="role" onchange="this.form.submit()" <% if (u.id === user.id || !canMutate) { %>disabled<% } %>> 67 <option value="kijker" <%= u.role === 'kijker' ? 'selected' : '' %>> kijker</option>68 <option value="member" <%= u.role === 'member' ? 'selected' : '' %>> member</option>69 <option value="admin" <%= u.role === 'admin' ? 'selected' : '' %>> admin</option>70 <option value="god" <%= u.role === 'god' ? 'selected' : '' %>> god</option>83 <option value="kijker" <%= u.role === 'kijker' ? 'selected' : '' %>><%= _u_r_kijker %></option> 84 <option value="member" <%= u.role === 'member' ? 'selected' : '' %>><%= _u_r_member %></option> 85 <option value="admin" <%= u.role === 'admin' ? 'selected' : '' %>><%= _u_r_admin %></option> 86 <option value="god" <%= u.role === 'god' ? 'selected' : '' %>><%= _u_r_god %></option> 71 87 </select> 72 88 </label> … … 75 91 <% if (u.id !== user.id && canMutate) { %> 76 92 <% var _warn = (u.post_count + u.site_count > 0) 77 ? ( 'Dit verwijdert ook hun site + ' + u.post_count + ' post(s).')93 ? (t('ausr.del_warn', { n: u.post_count }) + ' ') 78 94 : ''; %> 79 95 <form method="post" action="/admin/users/<%= u.id %>/delete" class="ax-delete-form" 80 onsubmit="return confirm(' Gebruiker <%= u.username %> verwijderen? <%= _warn %>Dit kan niet ongedaan worden.')">81 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label=" Verwijderen" title="Verwijderen">🗑</button>96 onsubmit="return confirm('<%= t('ausr.del_confirm', { name: u.username }) %> <%= _warn %><%= t('ausr.del_undo') %>')"> 97 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _u_del %>" title="<%= _u_del %>">🗑</button> 82 98 </form> 83 99 <% } else { %>
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)