Changeset 4885eab in Klonkt


Ignore:
Timestamp:
06/21/2026 03:05:14 PM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
5e61b17
Parents:
bbf9d1a
Message:

feat(i18n phase 6): remaining public + other pages translated (NL/EN/DE)

post-edit, account, newsletter, download, press kit /pers, fan-gate, link-in-bio,
my-site, password-reset (2), artists directory, auth-login Google error map,
user/hub-home/circle-feed+post/viewer-blocked/home/favourites/changelog/404/
type/tag/archive/prutter (inbox+conversation). embed-player skipped (no t
in that standalone route). 1026 view-keys cross-checked → 0 missing.

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

Location:
src
Files:
27 edited

Legend:

Unmodified
Added
Removed
  • src/services/i18n.js

    rbbf9d1a r4885eab  
    660660    'ahelp.s_updates_h': 'Updates',
    661661    '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.',
     662    // === Publieke + overige pagina's ===
     663    'pedit.title_new': 'Nieuwe post',
     664    'pedit.title_edit': 'Post bewerken',
     665    'pedit.f_title': 'Titel',
     666    'pedit.f_slug': 'Slug (URL)',
     667    'pedit.slug_placeholder': 'auto van titel als leeg',
     668    'pedit.f_tags': 'Tags',
     669    'pedit.tags_hint': '(komma-gescheiden)',
     670    'pedit.f_excerpt': 'Samenvatting & Cirkel Preview',
     671    'pedit.excerpt_hint': 'Wordt ook gebruikt als samenvatting in <strong>Cirkels</strong> (andere sites die je post tonen). Leeg laten = begin van de post.',
     672    'pedit.s_cover': 'Cover',
     673    'pedit.f_cover_url': 'Cover URL',
     674    'pedit.cover_url_placeholder': '/media/…  of https://…  (of upload met de knop)',
     675    'pedit.cover_upload_btn': 'Upload nieuwe cover',
     676    'pedit.s_content': 'Content',
     677    'pedit.content_hint': 'Sleep een afbeelding om in te voegen · selecteer tekst om op te maken',
     678    'pedit.tb_done': 'Klaar',
     679    'pedit.tb_done_title': 'Klaar met bewerken',
     680    'pedit.tb_bold': 'Vet',
     681    'pedit.tb_bold_title': 'Vet (Ctrl+B)',
     682    'pedit.tb_italic': 'Cursief',
     683    'pedit.tb_italic_title': 'Cursief (Ctrl+I)',
     684    'pedit.tb_underline': 'Onderstreept',
     685    'pedit.tb_h2': 'Kop',
     686    'pedit.tb_h3': 'Subkop',
     687    'pedit.tb_p': 'Paragraaf',
     688    'pedit.tb_ul': 'Lijst',
     689    'pedit.tb_ol': 'Genummerde lijst',
     690    'pedit.tb_quote': 'Quote',
     691    'pedit.tb_link': 'Link',
     692    'pedit.tb_link_title': 'Link (Ctrl+K)',
     693    'pedit.tb_code': 'Code',
     694    'pedit.tb_code_title': 'Code (inline)',
     695    'pedit.tb_image': 'Afbeelding',
     696    'pedit.tb_image_title': 'Afbeelding invoegen',
     697    'pedit.tb_track': 'Track',
     698    'pedit.tb_track_title': 'Track invoegen',
     699    'pedit.tb_playlist': 'Playlist',
     700    'pedit.tb_playlist_title': 'Playlist invoegen',
     701    'pedit.tb_embed': 'Media embedden',
     702    'pedit.tb_embed_title': 'Embed (YouTube, Spotify, SoundCloud, Vimeo…)',
     703    'pedit.tb_clear': 'Wis opmaak',
     704    'pedit.tb_fullscreen': 'Volledig scherm',
     705    'pedit.editor_aria': 'Content',
     706    'pedit.editor_placeholder': 'Begin met schrijven…',
     707    'pedit.tap_to_edit': 'Tik om te bewerken',
     708    'pedit.tap_to_write': 'Tik om te schrijven…',
     709    'pedit.chars': 'tekens',
     710    'pedit.s_publication': 'Publicatie',
     711    'pedit.f_status': 'Status',
     712    'pedit.status_published': 'Gepubliceerd',
     713    'pedit.status_draft': 'Concept',
     714    'pedit.status_archived': 'Gearchiveerd',
     715    'pedit.f_type': 'Type',
     716    'pedit.type_post': 'Post',
     717    'pedit.type_foto': 'Foto',
     718    'pedit.type_video': 'Video',
     719    'pedit.type_audio': 'Audio',
     720    'pedit.pin_label': 'Vastpinnen bovenaan',
     721    'pedit.pin_up': 'Hoger zetten',
     722    'pedit.pin_down': 'Lager zetten',
     723    'pedit.pin_top': 'bovenaan',
     724    'pedit.pin_nth_suffix': 'e van boven',
     725    'pedit.noindex_label': 'noindex (verberg voor zoekmachines)',
     726    'pedit.fan_only_label': 'Alleen voor ingelogde fans',
     727    'pedit.publish_at_label': 'Publiceren op (laat leeg = direct)',
     728    'pedit.scheduled_for': 'Ingepland voor {d}',
     729    'pedit.cancel': 'Annuleren',
     730    'pedit.publish': 'Publish',
     731    'pedit.save': 'Opslaan',
     732    'pedit.js_link_prompt': 'Link URL (https://… of /pad)',
     733    'pedit.js_uploading': 'Uploaden…',
     734    'pedit.js_uploaded': 'Geüpload',
     735    'pedit.js_inserted': 'Ingevoegd',
     736    'pedit.js_failed': 'Mislukt',
     737    'pedit.js_embed_prompt': 'Plak een media-URL om in te sluiten (YouTube, Spotify, SoundCloud, Vimeo, Apple Music, Bandcamp):',
     738    'pedit.js_embed_invalid': 'Geef een volledige URL (https://…).',
     739    'pedit.js_no_tracks_found': 'Geen tracks gevonden voor',
     740    'pedit.js_no_tracks_yet': 'Nog geen tracks. Upload via Beheer naar Audio.',
     741    'pedit.js_tracks_loading': 'Tracks laden…',
     742    'pedit.js_tracks_load_fail': 'Kon tracks niet laden',
     743    'pedit.js_playlist_editor_missing': 'Playlist editor niet geladen',
     744    'pedit.js_playlist_existing': 'Bestaande playlists:',
     745    'pedit.js_playlist_choose': 'Kies een nummer om in te voegen, leeg = nieuwe maken:',
     746    'pedit.chip_track': 'Track',
     747    'pedit.chip_album': 'Album:',
     748    'pedit.chip_playlist': 'Playlist:',
     749    'pedit.tp_title': 'Track invoegen',
     750    'pedit.tp_close': 'Sluiten',
     751    'pedit.tp_search_placeholder': 'Zoek op titel of artiest…',
     752    'pedit.tp_list_aria': 'Tracks',
     753    'acct.back_home': 'Terug naar home',
     754    'acct.title': 'Account',
     755    'acct.subtitle': 'Profiel en avatar.',
     756    'acct.viewer_mode': 'Kijker-modus',
     757    'acct.viewer_note': 'Dit is een demo-account. Je kunt alles bekijken, maar niets wijzigen — ook geen foto of bio.',
     758    'acct.profile': 'Profiel',
     759    'acct.avatar_change': 'Klik om je foto te wijzigen',
     760    'acct.member_since': 'Lid sinds',
     761    'acct.avatar_remove': 'Foto verwijderen',
     762    'acct.email': 'E-mailadres',
     763    'acct.email_ph': 'jij@email.nl',
     764    'acct.bio': 'Bio',
     765    'acct.bio_ph': 'Een korte regel over jezelf',
     766    'acct.bio_empty': 'Geen bio',
     767    'acct.save': 'Opslaan',
     768    'acct.site': 'Site',
     769    'acct.site_name': 'Site-naam',
     770    'acct.site_name_hint': '— getoond in de kop van je site',
     771    'acct.tagline': 'Tagline',
     772    'acct.tagline_hint': '— korte oneliner (optioneel)',
     773    'acct.site_save': 'Site opslaan',
     774    'acct.password_change': 'Wachtwoord wijzigen',
     775    'acct.password_current': 'Huidig wachtwoord',
     776    'acct.password_new': 'Nieuw wachtwoord',
     777    'acct.password_min': '(min 8 tekens)',
     778    'acct.password_confirm': 'Bevestig nieuw wachtwoord',
     779    'acct.google_login': 'Inloggen met Google',
     780    'acct.google_linked': '✓ Je Google-account is gekoppeld — je kunt ook met Google inloggen.',
     781    'acct.google_unlink': 'Google ontkoppelen',
     782    'acct.google_link_hint': 'Koppel je Google-account zodat je voortaan ook met één klik via Google kunt inloggen (naast je wachtwoord).',
     783    'acct.google_link': 'Koppel Google-account',
     784    'acct.login': 'Inloggen',
     785    'acct.login_google_only': 'Je bent ingelogd via Google ({email}). Er is geen apart wachtwoord.',
     786    'news.this_artist': 'deze artiest',
     787    'news.form_title': 'Blijf op de hoogte',
     788    'news.form_sub_before': 'Schrijf je in voor de nieuwsbrief van ',
     789    'news.form_sub_after': ' — nieuwe muziek, shows en updates, rechtstreeks in je inbox. Uitschrijven kan altijd met één klik.',
     790    'news.email_ph': 'jouw@email.nl',
     791    'news.subscribe': 'Inschrijven',
     792    'news.check_title': 'Bijna klaar ✉',
     793    'news.check_sub_before': 'We hebben een bevestigingsmail gestuurd naar ',
     794    'news.check_sub_after': '. Klik op de link in die mail om je inschrijving te bevestigen.',
     795    'news.your_address': 'je adres',
     796    'news.done_title': 'Je bent ingeschreven ✓',
     797    'news.done_sub_before': 'Bedankt — je staat op de lijst van ',
     798    'news.done_sub_after': '.',
     799    'news.confirmed_title': 'Inschrijving bevestigd ✓',
     800    'news.confirmed_sub_before': 'Top! Je ontvangt voortaan de nieuwsbrief van ',
     801    'news.confirmed_sub_after': '.',
     802    'news.unsubbed_title': 'Uitgeschreven',
     803    'news.unsubbed_sub': 'Je bent afgemeld. Je ontvangt geen nieuwsbrieven meer. Van gedachten veranderd? Je kunt je altijd opnieuw inschrijven.',
     804    'news.invalid_title': 'Ongeldig e-mailadres',
     805    'news.invalid_sub': 'Controleer je adres en probeer \'t opnieuw.',
     806    'news.back': '← Terug',
     807    'news.smtperror_title': 'Even niet gelukt',
     808    'news.smtperror_sub': 'De bevestigingsmail kon niet verstuurd worden. Probeer \'t later nog eens.',
     809    'news.badtoken_title': 'Link ongeldig of verlopen',
     810    'news.badtoken_sub': 'Deze link werkt niet meer. Schrijf je eventueel opnieuw in.',
     811    'news.to_subscribe': 'Naar inschrijven',
     812    'news.error_title': 'Er ging iets mis',
     813    'news.error_sub': 'Probeer \'t later nog eens.',
     814    'dl.ready_title': 'Bedankt! ⬇',
     815    'dl.ready_sub': 'Je download zou nu moeten starten. Gebeurt er niets?',
     816    'dl.manual': 'Download handmatig starten',
     817    'dl.download_btn': 'Download',
     818    'dl.capture_sub': 'Laat je e-mailadres achter en je krijgt het bestand. Je komt dan ook op de nieuwsbrieflijst — uitschrijven kan altijd.',
     819    'dl.email_ph': 'jouw@email.nl',
     820    'epk.kicker': 'Perskit',
     821    'epk.edit': 'Bewerken',
     822    'epk.contact_booking': 'Contact / boeking',
     823    'epk.view_site': 'Bekijk de site →',
     824    'epk.press_photo': 'Persfoto',
     825    'epk.most_played': 'Meest beluisterd',
     826    'epk.recent': 'Recent',
     827    'fgate.title': 'Alleen voor fans',
     828    'fgate.sub': 'Dit is exclusief voor ingelogde fans. Log in om het te bekijken.',
     829    'fgate.login': 'Inloggen / aanmelden',
     830    'lbio.empty': 'Nog geen links ingesteld.',
     831    'lbio.back_to_site': 'naar de site',
     832    'myst.overview': 'Overzicht',
     833    'myst.title': 'Mijn Klonkt Hub',
     834    'myst.quick_links_aria': 'Beheer-snelkoppelingen',
     835    'myst.new_post': 'Nieuwe post',
     836    'myst.appearance': 'Uiterlijk',
     837    'myst.comments': 'Reacties',
     838    'myst.view_site': 'Bekijk mijn site',
     839    'myst.account': 'Account',
     840    'myst.posts': 'Posts',
     841    'myst.published': 'Gepubliceerd',
     842    'myst.draft_count_one': '{n} concept',
     843    'myst.draft_count_many': '{n} concepten',
     844    'myst.draft_badge': 'Concept',
     845    'myst.untitled': '(zonder titel)',
     846    'myst.edit': 'Bewerken',
     847    'myst.view': 'Bekijk',
     848    'arst.title': 'Wachtwoord resetten',
     849    'arst.request_new': 'Vraag een nieuwe reset-link aan',
     850    'arst.back_login': 'Terug naar inloggen',
     851    'arst.set_for': 'Stel een nieuw wachtwoord in voor {username}.',
     852    'arst.new_pw': 'Nieuw wachtwoord (min 8 tekens)',
     853    'arst.confirm_pw': 'Bevestig nieuw wachtwoord',
     854    'arst.submit': 'Wachtwoord instellen',
     855    'arrq.title': 'Wachtwoord resetten',
     856    'arrq.sent': 'Als er een account bestaat voor dat e-mailadres, is er een reset-link verstuurd.',
     857    'arrq.no_mailserver': '<strong>Geen e-mailserver ingesteld</strong> — reset-link hieronder.',
     858    'arrq.no_mail_cli': 'Geen e-mail ingesteld? De beheerder kan ook <code>npm run reset-admin</code> draaien op de server.',
     859    'arrq.back_login': '← Terug naar inloggen',
     860    'arrq.tagline': 'Vul je e-mail in; we sturen een reset-link.',
     861    'arrq.email': 'E-mail',
     862    'arrq.submit': 'Stuur reset-link',
     863    'adir.home': 'Home',
     864    'adir.logout': 'Uitloggen',
     865    'adir.login': 'Inloggen',
     866    'adir.title': 'Leden',
     867    'adir.count_one': '{n} Klonkt',
     868    'adir.count_many': "{n} Klonkt's",
     869    'adir.search_ph': 'Zoek op naam…',
     870    'adir.search_aria': 'Zoek leden',
     871    'adir.search_btn': 'Zoeken',
     872    'adir.clear': 'Wissen',
     873    'adir.empty_q': 'Geen leden gevonden voor "{q}".',
     874    'adir.empty': 'Nog geen leden.',
     875    'adir.posts_one': '{n} post',
     876    'adir.posts_many': '{n} posts',
     877    'adir.pager_aria': 'Paginering',
     878    'adir.prev': '← Vorige',
     879    'adir.page_info': 'Pagina {page} van {pages}',
     880    'adir.next': 'Volgende →',
     881    'glog.admin_title': 'Dit is een beheerders-account',
     882    'glog.admin_body': 'Je logde in met Google, maar dit e-mailadres hoort bij de beheerder van deze site. Inloggen met Google is alleen voor luisteraars/fans — beheerders gebruiken altijd hun wachtwoord (zo kan een Google-login nooit per ongeluk beheerrechten geven).',
     883    'glog.admin_li1': '<strong>Ben je de beheerder?</strong> <a href="/auth/admin">Log in met je gebruikersnaam + wachtwoord</a>.',
     884    'glog.admin_li2': '<strong>Wil je voortaan met Google inloggen?</strong> Log eerst in met wachtwoord en <strong>koppel je Google-account</strong> onder Account → daarna werkt Google-login ook voor jou.',
     885    'glog.admin_li3': '<strong>Wil je als fan inloggen?</strong> Gebruik een <strong>ander Google-account</strong> (niet je beheerdersadres).',
     886    'glog.session_title': 'Login afgebroken',
     887    'glog.session_body': 'De Google-login is afgebroken of de sessie was verlopen. Probeer het gewoon opnieuw.',
     888    'glog.email_title': 'Geen geverifieerd e-mailadres',
     889    'glog.email_body': 'Je Google-account heeft geen geverifieerd e-mailadres. Verifieer je adres bij Google.',
     890    'glog.linked_title': 'Adres al gekoppeld',
     891    'glog.linked_body': 'Dit e-mailadres is al aan een ander Google-account gekoppeld. Gebruik dat account, of neem contact op met de beheerder.',
     892    'glog.unavailable_title': 'Google-login niet beschikbaar',
     893    'glog.unavailable_body': 'Inloggen met Google is op deze site (nog) niet ingesteld.',
     894    'glog.failed_title': 'Google-login mislukt',
     895    'glog.failed_body': 'Er ging iets mis bij het inloggen met Google. Probeer het opnieuw.',
     896    'pusr.post_one': 'post',
     897    'pusr.post_many': 'posts',
     898    'pusr.on_this_site': 'op deze site',
     899    'pusr.total': '{n} totaal',
     900    'pusr.joined': 'lid sinds {date}',
     901    'pusr.send_dm': 'Stuur DM',
     902    'pusr.empty': 'Nog geen posts op deze site.',
     903    'pusr.posts_heading': 'Posts',
     904    'pusr.untitled': '(zonder titel)',
     905    'phub.main_badge': 'Hoofdpagina',
     906    'phub.view_page': 'Bekijk de pagina',
     907    'phub.members': 'Leden',
     908    'phub.post_one': 'post',
     909    'phub.post_many': 'posts',
     910    'phub.all_members': 'Alle {n} leden',
     911    'phub.latest_posts': 'Laatste posts van onze leden.',
     912    'cfeed.title': 'Cirkel',
     913    'cfeed.sub': 'Wat er speelt bij de andere sites in mijn cirkel.',
     914    'cfeed.count': '{n} sites in mijn cirkel',
     915    'cfeed.empty': 'Nog niets in mijn cirkel.',
     916    'cfeed.grid_view': 'Grid-weergave',
     917    'cpost.back': 'Cirkel',
     918    'cpost.via': 'via',
     919    'cpost.read_more': 'Lees verder bij {source}',
     920    'vblk.title': 'Kijker-modus',
     921    'vblk.text_before': 'Dit is een alleen-lezen account. Je kunt alles bekijken, maar',
     922    'vblk.text_strong': 'niets wijzigen',
     923    'vblk.text_after': '— opslaan, uploaden, verwijderen en reageren zijn uitgeschakeld.',
     924    'vblk.back': 'Terug',
     925    'vblk.to_home': 'Naar de hoofdpagina',
     926    'phome.empty_title': 'Hier is het nog stil.',
     927    'phome.empty_sub': 'Nog geen posts. Spannend.',
     928    'phome.write_first': 'Schrijf je eerste post',
     929    'phome.grid_view': 'Grid-weergave',
     930    'fav.title': 'Favorieten',
     931    'fav.sub': "Posts die je hebt geliket. Tik op het ♥ bij een post om 'm hier te bewaren.",
     932    'fav.empty': 'Je hebt nog geen favorieten. Open een post en tik op het hartje ♡.',
     933    'chlog.back': 'Terug',
     934    'chlog.title': 'Wijzigingen',
     935    'chlog.app_version': 'App-versie',
     936    'chlog.fed_proto': 'Federatie-proto',
     937    'chlog.manage_updates': 'Updates beheren',
     938    'e404.title': 'Pagina niet gevonden',
     939    'e404.sub': 'Deze pagina bestaat niet (meer). Misschien is de link verouderd of verkeerd getypt.',
     940    'e404.home': 'Naar de homepagina',
     941    'e404.archive': 'Archief',
     942    'ptype.eyebrow': 'Type',
     943    'ptype.count_one': '{n} post',
     944    'ptype.count_many': '{n} posts',
     945    'ptype.empty': 'Nog geen posts van dit type.',
     946    'ptype.untitled': '(zonder titel)',
     947    'ptag.eyebrow': 'Tag',
     948    'ptag.count_one': '{n} post',
     949    'ptag.count_many': '{n} posts',
     950    'ptag.empty': 'Nog geen posts met deze tag.',
     951    'ptag.untitled': '(zonder titel)',
     952    'parch.back': 'Terug',
     953    'parch.title': 'Archief',
     954    'parch.count_one': '{n} bericht',
     955    'parch.count_many': '{n} berichten',
     956    'parch.empty': 'Nog geen berichten.',
     957    'prin.tagline': 'Je directe berichten op deze site.',
     958    'prin.empty': 'Nog geen gesprekken. Open iemands profiel en klik op "DM sturen" om er een te beginnen.',
     959    'prin.empty_conv': 'Leeg gesprek',
     960    'prcv.back_aria': 'Terug naar inbox',
     961    'prcv.inbox': 'Inbox',
     962    'prcv.unknown': 'Onbekend',
     963    'prcv.view_profile': 'Bekijk profiel',
     964    'prcv.placeholder': 'Bericht…',
     965    'prcv.send': 'Stuur',
    662966  },
    663967  en: {
     
    13031607    'ahelp.s_updates_h': 'Updates',
    13041608    '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.',
     1609    'pedit.title_new': 'New post',
     1610    'pedit.title_edit': 'Edit post',
     1611    'pedit.f_title': 'Title',
     1612    'pedit.f_slug': 'Slug (URL)',
     1613    'pedit.slug_placeholder': 'auto from title if empty',
     1614    'pedit.f_tags': 'Tags',
     1615    'pedit.tags_hint': '(comma-separated)',
     1616    'pedit.f_excerpt': 'Summary & Circle Preview',
     1617    'pedit.excerpt_hint': 'Also used as the summary in <strong>Circles</strong> (other sites that show your post). Leave empty = start of the post.',
     1618    'pedit.s_cover': 'Cover',
     1619    'pedit.f_cover_url': 'Cover URL',
     1620    'pedit.cover_url_placeholder': '/media/…  or https://…  (or upload with the button)',
     1621    'pedit.cover_upload_btn': 'Upload new cover',
     1622    'pedit.s_content': 'Content',
     1623    'pedit.content_hint': 'Drag an image to insert · select text to format',
     1624    'pedit.tb_done': 'Done',
     1625    'pedit.tb_done_title': 'Done editing',
     1626    'pedit.tb_bold': 'Bold',
     1627    'pedit.tb_bold_title': 'Bold (Ctrl+B)',
     1628    'pedit.tb_italic': 'Italic',
     1629    'pedit.tb_italic_title': 'Italic (Ctrl+I)',
     1630    'pedit.tb_underline': 'Underline',
     1631    'pedit.tb_h2': 'Heading',
     1632    'pedit.tb_h3': 'Subheading',
     1633    'pedit.tb_p': 'Paragraph',
     1634    'pedit.tb_ul': 'List',
     1635    'pedit.tb_ol': 'Numbered list',
     1636    'pedit.tb_quote': 'Quote',
     1637    'pedit.tb_link': 'Link',
     1638    'pedit.tb_link_title': 'Link (Ctrl+K)',
     1639    'pedit.tb_code': 'Code',
     1640    'pedit.tb_code_title': 'Code (inline)',
     1641    'pedit.tb_image': 'Image',
     1642    'pedit.tb_image_title': 'Insert image',
     1643    'pedit.tb_track': 'Track',
     1644    'pedit.tb_track_title': 'Insert track',
     1645    'pedit.tb_playlist': 'Playlist',
     1646    'pedit.tb_playlist_title': 'Insert playlist',
     1647    'pedit.tb_embed': 'Embed media',
     1648    'pedit.tb_embed_title': 'Embed (YouTube, Spotify, SoundCloud, Vimeo…)',
     1649    'pedit.tb_clear': 'Clear formatting',
     1650    'pedit.tb_fullscreen': 'Full screen',
     1651    'pedit.editor_aria': 'Content',
     1652    'pedit.editor_placeholder': 'Start writing…',
     1653    'pedit.tap_to_edit': 'Tap to edit',
     1654    'pedit.tap_to_write': 'Tap to write…',
     1655    'pedit.chars': 'characters',
     1656    'pedit.s_publication': 'Publication',
     1657    'pedit.f_status': 'Status',
     1658    'pedit.status_published': 'Published',
     1659    'pedit.status_draft': 'Draft',
     1660    'pedit.status_archived': 'Archived',
     1661    'pedit.f_type': 'Type',
     1662    'pedit.type_post': 'Post',
     1663    'pedit.type_foto': 'Photo',
     1664    'pedit.type_video': 'Video',
     1665    'pedit.type_audio': 'Audio',
     1666    'pedit.pin_label': 'Pin to top',
     1667    'pedit.pin_up': 'Move up',
     1668    'pedit.pin_down': 'Move down',
     1669    'pedit.pin_top': 'at the top',
     1670    'pedit.pin_nth_suffix': 'th from top',
     1671    'pedit.noindex_label': 'noindex (hide from search engines)',
     1672    'pedit.fan_only_label': 'Logged-in fans only',
     1673    'pedit.publish_at_label': 'Publish at (leave empty = immediately)',
     1674    'pedit.scheduled_for': 'Scheduled for {d}',
     1675    'pedit.cancel': 'Cancel',
     1676    'pedit.publish': 'Publish',
     1677    'pedit.save': 'Save',
     1678    'pedit.js_link_prompt': 'Link URL (https://… or /path)',
     1679    'pedit.js_uploading': 'Uploading…',
     1680    'pedit.js_uploaded': 'Uploaded',
     1681    'pedit.js_inserted': 'Inserted',
     1682    'pedit.js_failed': 'Failed',
     1683    'pedit.js_embed_prompt': 'Paste a media URL to embed (YouTube, Spotify, SoundCloud, Vimeo, Apple Music, Bandcamp):',
     1684    'pedit.js_embed_invalid': 'Enter a full URL (https://…).',
     1685    'pedit.js_no_tracks_found': 'No tracks found for',
     1686    'pedit.js_no_tracks_yet': 'No tracks yet. Upload via Admin then Audio.',
     1687    'pedit.js_tracks_loading': 'Loading tracks…',
     1688    'pedit.js_tracks_load_fail': 'Could not load tracks',
     1689    'pedit.js_playlist_editor_missing': 'Playlist editor not loaded',
     1690    'pedit.js_playlist_existing': 'Existing playlists:',
     1691    'pedit.js_playlist_choose': 'Choose a number to insert, empty = create new:',
     1692    'pedit.chip_track': 'Track',
     1693    'pedit.chip_album': 'Album:',
     1694    'pedit.chip_playlist': 'Playlist:',
     1695    'pedit.tp_title': 'Insert track',
     1696    'pedit.tp_close': 'Close',
     1697    'pedit.tp_search_placeholder': 'Search by title or artist…',
     1698    'pedit.tp_list_aria': 'Tracks',
     1699    'acct.back_home': 'Back to home',
     1700    'acct.title': 'Account',
     1701    'acct.subtitle': 'Profile and avatar.',
     1702    'acct.viewer_mode': 'Viewer mode',
     1703    'acct.viewer_note': 'This is a demo account. You can view everything, but change nothing — not even your photo or bio.',
     1704    'acct.profile': 'Profile',
     1705    'acct.avatar_change': 'Click to change your photo',
     1706    'acct.member_since': 'Member since',
     1707    'acct.avatar_remove': 'Remove photo',
     1708    'acct.email': 'Email address',
     1709    'acct.email_ph': 'you@email.com',
     1710    'acct.bio': 'Bio',
     1711    'acct.bio_ph': 'A short line about yourself',
     1712    'acct.bio_empty': 'No bio',
     1713    'acct.save': 'Save',
     1714    'acct.site': 'Site',
     1715    'acct.site_name': 'Site name',
     1716    'acct.site_name_hint': '— shown in your site\'s header',
     1717    'acct.tagline': 'Tagline',
     1718    'acct.tagline_hint': '— short one-liner (optional)',
     1719    'acct.site_save': 'Save site',
     1720    'acct.password_change': 'Change password',
     1721    'acct.password_current': 'Current password',
     1722    'acct.password_new': 'New password',
     1723    'acct.password_min': '(min 8 characters)',
     1724    'acct.password_confirm': 'Confirm new password',
     1725    'acct.google_login': 'Sign in with Google',
     1726    'acct.google_linked': '✓ Your Google account is linked — you can also sign in with Google.',
     1727    'acct.google_unlink': 'Unlink Google',
     1728    'acct.google_link_hint': 'Link your Google account so you can also sign in with one click via Google (alongside your password).',
     1729    'acct.google_link': 'Link Google account',
     1730    'acct.login': 'Sign in',
     1731    'acct.login_google_only': 'You are signed in via Google ({email}). There is no separate password.',
     1732    'news.this_artist': 'this artist',
     1733    'news.form_title': 'Stay in the loop',
     1734    'news.form_sub_before': 'Subscribe to the newsletter from ',
     1735    'news.form_sub_after': ' — new music, shows and updates, straight to your inbox. You can unsubscribe anytime with one click.',
     1736    'news.email_ph': 'you@email.com',
     1737    'news.subscribe': 'Subscribe',
     1738    'news.check_title': 'Almost done ✉',
     1739    'news.check_sub_before': 'We\'ve sent a confirmation email to ',
     1740    'news.check_sub_after': '. Click the link in that email to confirm your subscription.',
     1741    'news.your_address': 'your address',
     1742    'news.done_title': 'You\'re subscribed ✓',
     1743    'news.done_sub_before': 'Thanks — you\'re on the list of ',
     1744    'news.done_sub_after': '.',
     1745    'news.confirmed_title': 'Subscription confirmed ✓',
     1746    'news.confirmed_sub_before': 'Great! You\'ll now receive the newsletter from ',
     1747    'news.confirmed_sub_after': '.',
     1748    'news.unsubbed_title': 'Unsubscribed',
     1749    'news.unsubbed_sub': 'You\'ve been unsubscribed. You won\'t receive any more newsletters. Changed your mind? You can always subscribe again.',
     1750    'news.invalid_title': 'Invalid email address',
     1751    'news.invalid_sub': 'Please check your address and try again.',
     1752    'news.back': '← Back',
     1753    'news.smtperror_title': 'That didn\'t work',
     1754    'news.smtperror_sub': 'The confirmation email couldn\'t be sent. Please try again later.',
     1755    'news.badtoken_title': 'Link invalid or expired',
     1756    'news.badtoken_sub': 'This link no longer works. Feel free to subscribe again.',
     1757    'news.to_subscribe': 'To subscribe',
     1758    'news.error_title': 'Something went wrong',
     1759    'news.error_sub': 'Please try again later.',
     1760    'dl.ready_title': 'Thanks! ⬇',
     1761    'dl.ready_sub': 'Your download should start now. Nothing happening?',
     1762    'dl.manual': 'Start download manually',
     1763    'dl.download_btn': 'Download',
     1764    'dl.capture_sub': 'Leave your email address and you\'ll get the file. You\'ll also be added to the newsletter list — you can unsubscribe anytime.',
     1765    'dl.email_ph': 'you@email.com',
     1766    'epk.kicker': 'Press kit',
     1767    'epk.edit': 'Edit',
     1768    'epk.contact_booking': 'Contact / booking',
     1769    'epk.view_site': 'View the site →',
     1770    'epk.press_photo': 'Press photo',
     1771    'epk.most_played': 'Most played',
     1772    'epk.recent': 'Recent',
     1773    'fgate.title': 'Fans only',
     1774    'fgate.sub': 'This is exclusive to logged-in fans. Log in to view it.',
     1775    'fgate.login': 'Log in / sign up',
     1776    'lbio.empty': 'No links set up yet.',
     1777    'lbio.back_to_site': 'back to the site',
     1778    'myst.overview': 'Overview',
     1779    'myst.title': 'My Klonkt Hub',
     1780    'myst.quick_links_aria': 'Admin shortcuts',
     1781    'myst.new_post': 'New post',
     1782    'myst.appearance': 'Appearance',
     1783    'myst.comments': 'Comments',
     1784    'myst.view_site': 'View my site',
     1785    'myst.account': 'Account',
     1786    'myst.posts': 'Posts',
     1787    'myst.published': 'Published',
     1788    'myst.draft_count_one': '{n} draft',
     1789    'myst.draft_count_many': '{n} drafts',
     1790    'myst.draft_badge': 'Draft',
     1791    'myst.untitled': '(untitled)',
     1792    'myst.edit': 'Edit',
     1793    'myst.view': 'View',
     1794    'arst.title': 'Reset password',
     1795    'arst.request_new': 'Request a new reset link',
     1796    'arst.back_login': 'Back to login',
     1797    'arst.set_for': 'Set a new password for {username}.',
     1798    'arst.new_pw': 'New password (min 8 characters)',
     1799    'arst.confirm_pw': 'Confirm new password',
     1800    'arst.submit': 'Set password',
     1801    'arrq.title': 'Reset password',
     1802    'arrq.sent': 'If an account exists for that email address, a reset link has been sent.',
     1803    'arrq.no_mailserver': '<strong>No mail server configured</strong> — reset link below.',
     1804    'arrq.no_mail_cli': 'No email set up? The administrator can also run <code>npm run reset-admin</code> on the server.',
     1805    'arrq.back_login': '← Back to login',
     1806    'arrq.tagline': 'Enter your email and we\'ll send you a reset link.',
     1807    'arrq.email': 'Email',
     1808    'arrq.submit': 'Send reset link',
     1809    'adir.home': 'Home',
     1810    'adir.logout': 'Log out',
     1811    'adir.login': 'Log in',
     1812    'adir.title': 'Members',
     1813    'adir.count_one': '{n} Klonkt',
     1814    'adir.count_many': "{n} Klonkt's",
     1815    'adir.search_ph': 'Search by name…',
     1816    'adir.search_aria': 'Search members',
     1817    'adir.search_btn': 'Search',
     1818    'adir.clear': 'Clear',
     1819    'adir.empty_q': 'No members found for "{q}".',
     1820    'adir.empty': 'No members yet.',
     1821    'adir.posts_one': '{n} post',
     1822    'adir.posts_many': '{n} posts',
     1823    'adir.pager_aria': 'Pagination',
     1824    'adir.prev': '← Previous',
     1825    'adir.page_info': 'Page {page} of {pages}',
     1826    'adir.next': 'Next →',
     1827    'glog.admin_title': 'This is an administrator account',
     1828    'glog.admin_body': 'You signed in with Google, but this email address belongs to the administrator of this site. Google sign-in is only for listeners/fans — administrators always use their password (so a Google login can never accidentally grant admin rights).',
     1829    'glog.admin_li1': '<strong>Are you the administrator?</strong> <a href="/auth/admin">Log in with your username + password</a>.',
     1830    'glog.admin_li2': '<strong>Want to sign in with Google from now on?</strong> Log in with your password first and <strong>link your Google account</strong> under Account → after that Google sign-in works for you too.',
     1831    'glog.admin_li3': '<strong>Want to sign in as a fan?</strong> Use a <strong>different Google account</strong> (not your administrator address).',
     1832    'glog.session_title': 'Login interrupted',
     1833    'glog.session_body': 'The Google login was interrupted or the session had expired. Just try again.',
     1834    'glog.email_title': 'No verified email address',
     1835    'glog.email_body': 'Your Google account has no verified email address. Verify your address with Google.',
     1836    'glog.linked_title': 'Address already linked',
     1837    'glog.linked_body': 'This email address is already linked to another Google account. Use that account, or contact the administrator.',
     1838    'glog.unavailable_title': 'Google sign-in unavailable',
     1839    'glog.unavailable_body': 'Google sign-in has not (yet) been set up on this site.',
     1840    'glog.failed_title': 'Google sign-in failed',
     1841    'glog.failed_body': 'Something went wrong signing in with Google. Please try again.',
     1842    'pusr.post_one': 'post',
     1843    'pusr.post_many': 'posts',
     1844    'pusr.on_this_site': 'on this site',
     1845    'pusr.total': '{n} total',
     1846    'pusr.joined': 'joined {date}',
     1847    'pusr.send_dm': 'Send DM',
     1848    'pusr.empty': 'No posts on this site yet.',
     1849    'pusr.posts_heading': 'Posts',
     1850    'pusr.untitled': '(untitled)',
     1851    'phub.main_badge': 'Main page',
     1852    'phub.view_page': 'View the page',
     1853    'phub.members': 'Members',
     1854    'phub.post_one': 'post',
     1855    'phub.post_many': 'posts',
     1856    'phub.all_members': 'All {n} members',
     1857    'phub.latest_posts': 'Latest posts from our members.',
     1858    'cfeed.title': 'Circle',
     1859    'cfeed.sub': "What's happening at the other sites in my circle.",
     1860    'cfeed.count': '{n} sites in my circle',
     1861    'cfeed.empty': 'Nothing in my circle yet.',
     1862    'cfeed.grid_view': 'Grid view',
     1863    'cpost.back': 'Circle',
     1864    'cpost.via': 'via',
     1865    'cpost.read_more': 'Read more at {source}',
     1866    'vblk.title': 'Viewer mode',
     1867    'vblk.text_before': 'This is a read-only account. You can view everything, but',
     1868    'vblk.text_strong': 'cannot change anything',
     1869    'vblk.text_after': '— saving, uploading, deleting and commenting are disabled.',
     1870    'vblk.back': 'Back',
     1871    'vblk.to_home': 'Go to the main page',
     1872    'phome.empty_title': "It's quiet here for now.",
     1873    'phome.empty_sub': 'No posts yet. Exciting.',
     1874    'phome.write_first': 'Write your first post',
     1875    'phome.grid_view': 'Grid view',
     1876    'fav.title': 'Favorites',
     1877    'fav.sub': 'Posts you have liked. Tap the ♥ on a post to save it here.',
     1878    'fav.empty': 'You have no favorites yet. Open a post and tap the heart ♡.',
     1879    'chlog.back': 'Back',
     1880    'chlog.title': 'Changes',
     1881    'chlog.app_version': 'App version',
     1882    'chlog.fed_proto': 'Federation proto',
     1883    'chlog.manage_updates': 'Manage updates',
     1884    'e404.title': 'Page not found',
     1885    'e404.sub': 'This page no longer exists. The link may be outdated or mistyped.',
     1886    'e404.home': 'Go to the homepage',
     1887    'e404.archive': 'Archive',
     1888    'ptype.eyebrow': 'Type',
     1889    'ptype.count_one': '{n} post',
     1890    'ptype.count_many': '{n} posts',
     1891    'ptype.empty': 'No posts of this type yet.',
     1892    'ptype.untitled': '(untitled)',
     1893    'ptag.eyebrow': 'Tag',
     1894    'ptag.count_one': '{n} post',
     1895    'ptag.count_many': '{n} posts',
     1896    'ptag.empty': 'No posts with this tag yet.',
     1897    'ptag.untitled': '(untitled)',
     1898    'parch.back': 'Back',
     1899    'parch.title': 'Archive',
     1900    'parch.count_one': '{n} post',
     1901    'parch.count_many': '{n} posts',
     1902    'parch.empty': 'No posts yet.',
     1903    'prin.tagline': 'Your direct messages on this site.',
     1904    'prin.empty': 'No conversations yet. Open someone\'s profile and click "Send DM" to start one.',
     1905    'prin.empty_conv': 'Empty conversation',
     1906    'prcv.back_aria': 'Back to inbox',
     1907    'prcv.inbox': 'Inbox',
     1908    'prcv.unknown': 'Unknown',
     1909    'prcv.view_profile': 'View profile',
     1910    'prcv.placeholder': 'Message…',
     1911    'prcv.send': 'Send',
    13051912  },
    13061913  de: {
     
    19462553    'ahelp.s_updates_h': 'Updates',
    19472554    '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.',
     2555    'pedit.title_new': 'Neuer Beitrag',
     2556    'pedit.title_edit': 'Beitrag bearbeiten',
     2557    'pedit.f_title': 'Titel',
     2558    'pedit.f_slug': 'Slug (URL)',
     2559    'pedit.slug_placeholder': 'automatisch aus Titel, wenn leer',
     2560    'pedit.f_tags': 'Tags',
     2561    'pedit.tags_hint': '(kommagetrennt)',
     2562    'pedit.f_excerpt': 'Zusammenfassung & Zirkel-Vorschau',
     2563    'pedit.excerpt_hint': 'Wird auch als Zusammenfassung in <strong>Zirkeln</strong> verwendet (andere Seiten, die deinen Beitrag zeigen). Leer lassen = Anfang des Beitrags.',
     2564    'pedit.s_cover': 'Titelbild',
     2565    'pedit.f_cover_url': 'Titelbild-URL',
     2566    'pedit.cover_url_placeholder': '/media/…  oder https://…  (oder per Schaltfläche hochladen)',
     2567    'pedit.cover_upload_btn': 'Neues Titelbild hochladen',
     2568    'pedit.s_content': 'Inhalt',
     2569    'pedit.content_hint': 'Bild ziehen zum Einfügen · Text markieren zum Formatieren',
     2570    'pedit.tb_done': 'Fertig',
     2571    'pedit.tb_done_title': 'Bearbeitung beenden',
     2572    'pedit.tb_bold': 'Fett',
     2573    'pedit.tb_bold_title': 'Fett (Strg+B)',
     2574    'pedit.tb_italic': 'Kursiv',
     2575    'pedit.tb_italic_title': 'Kursiv (Strg+I)',
     2576    'pedit.tb_underline': 'Unterstrichen',
     2577    'pedit.tb_h2': 'Überschrift',
     2578    'pedit.tb_h3': 'Zwischenüberschrift',
     2579    'pedit.tb_p': 'Absatz',
     2580    'pedit.tb_ul': 'Liste',
     2581    'pedit.tb_ol': 'Nummerierte Liste',
     2582    'pedit.tb_quote': 'Zitat',
     2583    'pedit.tb_link': 'Link',
     2584    'pedit.tb_link_title': 'Link (Strg+K)',
     2585    'pedit.tb_code': 'Code',
     2586    'pedit.tb_code_title': 'Code (inline)',
     2587    'pedit.tb_image': 'Bild',
     2588    'pedit.tb_image_title': 'Bild einfügen',
     2589    'pedit.tb_track': 'Track',
     2590    'pedit.tb_track_title': 'Track einfügen',
     2591    'pedit.tb_playlist': 'Playlist',
     2592    'pedit.tb_playlist_title': 'Playlist einfügen',
     2593    'pedit.tb_embed': 'Medien einbetten',
     2594    'pedit.tb_embed_title': 'Einbetten (YouTube, Spotify, SoundCloud, Vimeo…)',
     2595    'pedit.tb_clear': 'Formatierung entfernen',
     2596    'pedit.tb_fullscreen': 'Vollbild',
     2597    'pedit.editor_aria': 'Inhalt',
     2598    'pedit.editor_placeholder': 'Schreib los…',
     2599    'pedit.tap_to_edit': 'Zum Bearbeiten tippen',
     2600    'pedit.tap_to_write': 'Zum Schreiben tippen…',
     2601    'pedit.chars': 'Zeichen',
     2602    'pedit.s_publication': 'Veröffentlichung',
     2603    'pedit.f_status': 'Status',
     2604    'pedit.status_published': 'Veröffentlicht',
     2605    'pedit.status_draft': 'Entwurf',
     2606    'pedit.status_archived': 'Archiviert',
     2607    'pedit.f_type': 'Typ',
     2608    'pedit.type_post': 'Beitrag',
     2609    'pedit.type_foto': 'Foto',
     2610    'pedit.type_video': 'Video',
     2611    'pedit.type_audio': 'Audio',
     2612    'pedit.pin_label': 'Oben anheften',
     2613    'pedit.pin_up': 'Nach oben',
     2614    'pedit.pin_down': 'Nach unten',
     2615    'pedit.pin_top': 'ganz oben',
     2616    'pedit.pin_nth_suffix': '. von oben',
     2617    'pedit.noindex_label': 'noindex (vor Suchmaschinen verbergen)',
     2618    'pedit.fan_only_label': 'Nur für angemeldete Fans',
     2619    'pedit.publish_at_label': 'Veröffentlichen am (leer lassen = sofort)',
     2620    'pedit.scheduled_for': 'Geplant für {d}',
     2621    'pedit.cancel': 'Abbrechen',
     2622    'pedit.publish': 'Veröffentlichen',
     2623    'pedit.save': 'Speichern',
     2624    'pedit.js_link_prompt': 'Link-URL (https://… oder /pfad)',
     2625    'pedit.js_uploading': 'Wird hochgeladen…',
     2626    'pedit.js_uploaded': 'Hochgeladen',
     2627    'pedit.js_inserted': 'Eingefügt',
     2628    'pedit.js_failed': 'Fehlgeschlagen',
     2629    'pedit.js_embed_prompt': 'Füge eine Medien-URL zum Einbetten ein (YouTube, Spotify, SoundCloud, Vimeo, Apple Music, Bandcamp):',
     2630    'pedit.js_embed_invalid': 'Gib eine vollständige URL ein (https://…).',
     2631    'pedit.js_no_tracks_found': 'Keine Tracks gefunden für',
     2632    'pedit.js_no_tracks_yet': 'Noch keine Tracks. Lade welche über Verwaltung dann Audio hoch.',
     2633    'pedit.js_tracks_loading': 'Tracks werden geladen…',
     2634    'pedit.js_tracks_load_fail': 'Tracks konnten nicht geladen werden',
     2635    'pedit.js_playlist_editor_missing': 'Playlist-Editor nicht geladen',
     2636    'pedit.js_playlist_existing': 'Vorhandene Playlists:',
     2637    'pedit.js_playlist_choose': 'Wähle eine Nummer zum Einfügen, leer = neue erstellen:',
     2638    'pedit.chip_track': 'Track',
     2639    'pedit.chip_album': 'Album:',
     2640    'pedit.chip_playlist': 'Playlist:',
     2641    'pedit.tp_title': 'Track einfügen',
     2642    'pedit.tp_close': 'Schließen',
     2643    'pedit.tp_search_placeholder': 'Nach Titel oder Künstler suchen…',
     2644    'pedit.tp_list_aria': 'Tracks',
     2645    'acct.back_home': 'Zurück zur Startseite',
     2646    'acct.title': 'Konto',
     2647    'acct.subtitle': 'Profil und Avatar.',
     2648    'acct.viewer_mode': 'Betrachter-Modus',
     2649    'acct.viewer_note': 'Dies ist ein Demo-Konto. Du kannst alles ansehen, aber nichts ändern — auch kein Foto oder keine Bio.',
     2650    'acct.profile': 'Profil',
     2651    'acct.avatar_change': 'Zum Ändern deines Fotos klicken',
     2652    'acct.member_since': 'Mitglied seit',
     2653    'acct.avatar_remove': 'Foto entfernen',
     2654    'acct.email': 'E-Mail-Adresse',
     2655    'acct.email_ph': 'du@email.de',
     2656    'acct.bio': 'Bio',
     2657    'acct.bio_ph': 'Eine kurze Zeile über dich',
     2658    'acct.bio_empty': 'Keine Bio',
     2659    'acct.save': 'Speichern',
     2660    'acct.site': 'Seite',
     2661    'acct.site_name': 'Seitenname',
     2662    'acct.site_name_hint': '— wird in der Kopfzeile deiner Seite angezeigt',
     2663    'acct.tagline': 'Slogan',
     2664    'acct.tagline_hint': '— kurzer Einzeiler (optional)',
     2665    'acct.site_save': 'Seite speichern',
     2666    'acct.password_change': 'Passwort ändern',
     2667    'acct.password_current': 'Aktuelles Passwort',
     2668    'acct.password_new': 'Neues Passwort',
     2669    'acct.password_min': '(mind. 8 Zeichen)',
     2670    'acct.password_confirm': 'Neues Passwort bestätigen',
     2671    'acct.google_login': 'Mit Google anmelden',
     2672    'acct.google_linked': '✓ Dein Google-Konto ist verknüpft — du kannst dich auch mit Google anmelden.',
     2673    'acct.google_unlink': 'Google trennen',
     2674    'acct.google_link_hint': 'Verknüpfe dein Google-Konto, damit du dich künftig auch mit einem Klick über Google anmelden kannst (zusätzlich zu deinem Passwort).',
     2675    'acct.google_link': 'Google-Konto verknüpfen',
     2676    'acct.login': 'Anmelden',
     2677    'acct.login_google_only': 'Du bist über Google angemeldet ({email}). Es gibt kein separates Passwort.',
     2678    'news.this_artist': 'diese:r Künstler:in',
     2679    'news.form_title': 'Bleib auf dem Laufenden',
     2680    'news.form_sub_before': 'Abonniere den Newsletter von ',
     2681    'news.form_sub_after': ' — neue Musik, Konzerte und Updates, direkt in dein Postfach. Abmelden ist jederzeit mit einem Klick möglich.',
     2682    'news.email_ph': 'du@email.de',
     2683    'news.subscribe': 'Abonnieren',
     2684    'news.check_title': 'Fast geschafft ✉',
     2685    'news.check_sub_before': 'Wir haben eine Bestätigungs-E-Mail gesendet an ',
     2686    'news.check_sub_after': '. Klicke auf den Link in dieser E-Mail, um dein Abo zu bestätigen.',
     2687    'news.your_address': 'deine Adresse',
     2688    'news.done_title': 'Du bist angemeldet ✓',
     2689    'news.done_sub_before': 'Danke — du stehst auf der Liste von ',
     2690    'news.done_sub_after': '.',
     2691    'news.confirmed_title': 'Abo bestätigt ✓',
     2692    'news.confirmed_sub_before': 'Super! Du erhältst ab jetzt den Newsletter von ',
     2693    'news.confirmed_sub_after': '.',
     2694    'news.unsubbed_title': 'Abgemeldet',
     2695    'news.unsubbed_sub': 'Du wurdest abgemeldet. Du erhältst keine Newsletter mehr. Anders überlegt? Du kannst dich jederzeit wieder anmelden.',
     2696    'news.invalid_title': 'Ungültige E-Mail-Adresse',
     2697    'news.invalid_sub': 'Bitte überprüfe deine Adresse und versuche es erneut.',
     2698    'news.back': '← Zurück',
     2699    'news.smtperror_title': 'Hat gerade nicht geklappt',
     2700    'news.smtperror_sub': 'Die Bestätigungs-E-Mail konnte nicht gesendet werden. Bitte versuche es später noch einmal.',
     2701    'news.badtoken_title': 'Link ungültig oder abgelaufen',
     2702    'news.badtoken_sub': 'Dieser Link funktioniert nicht mehr. Melde dich bei Bedarf erneut an.',
     2703    'news.to_subscribe': 'Zur Anmeldung',
     2704    'news.error_title': 'Etwas ist schiefgelaufen',
     2705    'news.error_sub': 'Bitte versuche es später noch einmal.',
     2706    'dl.ready_title': 'Danke! ⬇',
     2707    'dl.ready_sub': 'Dein Download sollte jetzt starten. Passiert nichts?',
     2708    'dl.manual': 'Download manuell starten',
     2709    'dl.download_btn': 'Download',
     2710    'dl.capture_sub': 'Hinterlasse deine E-Mail-Adresse und du erhältst die Datei. Du kommst damit auch auf die Newsletter-Liste — abmelden ist jederzeit möglich.',
     2711    'dl.email_ph': 'du@email.de',
     2712    'epk.kicker': 'Pressekit',
     2713    'epk.edit': 'Bearbeiten',
     2714    'epk.contact_booking': 'Kontakt / Buchung',
     2715    'epk.view_site': 'Zur Seite →',
     2716    'epk.press_photo': 'Pressefoto',
     2717    'epk.most_played': 'Meistgehört',
     2718    'epk.recent': 'Aktuell',
     2719    'fgate.title': 'Nur für Fans',
     2720    'fgate.sub': 'Dies ist exklusiv für eingeloggte Fans. Melde dich an, um es anzusehen.',
     2721    'fgate.login': 'Anmelden / Registrieren',
     2722    'lbio.empty': 'Noch keine Links eingerichtet.',
     2723    'lbio.back_to_site': 'zurück zur Seite',
     2724    'myst.overview': 'Übersicht',
     2725    'myst.title': 'Mein Klonkt Hub',
     2726    'myst.quick_links_aria': 'Verwaltungs-Schnellzugriffe',
     2727    'myst.new_post': 'Neuer Beitrag',
     2728    'myst.appearance': 'Erscheinungsbild',
     2729    'myst.comments': 'Kommentare',
     2730    'myst.view_site': 'Meine Seite ansehen',
     2731    'myst.account': 'Konto',
     2732    'myst.posts': 'Beiträge',
     2733    'myst.published': 'Veröffentlicht',
     2734    'myst.draft_count_one': '{n} Entwurf',
     2735    'myst.draft_count_many': '{n} Entwürfe',
     2736    'myst.draft_badge': 'Entwurf',
     2737    'myst.untitled': '(ohne Titel)',
     2738    'myst.edit': 'Bearbeiten',
     2739    'myst.view': 'Ansehen',
     2740    'arst.title': 'Passwort zurücksetzen',
     2741    'arst.request_new': 'Neuen Reset-Link anfordern',
     2742    'arst.back_login': 'Zurück zur Anmeldung',
     2743    'arst.set_for': 'Lege ein neues Passwort für {username} fest.',
     2744    'arst.new_pw': 'Neues Passwort (mind. 8 Zeichen)',
     2745    'arst.confirm_pw': 'Neues Passwort bestätigen',
     2746    'arst.submit': 'Passwort festlegen',
     2747    'arrq.title': 'Passwort zurücksetzen',
     2748    'arrq.sent': 'Falls für diese E-Mail-Adresse ein Konto besteht, wurde ein Reset-Link gesendet.',
     2749    'arrq.no_mailserver': '<strong>Kein Mailserver eingerichtet</strong> — Reset-Link unten.',
     2750    'arrq.no_mail_cli': 'Keine E-Mail eingerichtet? Der Administrator kann auch <code>npm run reset-admin</code> auf dem Server ausführen.',
     2751    'arrq.back_login': '← Zurück zur Anmeldung',
     2752    'arrq.tagline': 'Gib deine E-Mail-Adresse ein; wir senden dir einen Reset-Link.',
     2753    'arrq.email': 'E-Mail',
     2754    'arrq.submit': 'Reset-Link senden',
     2755    'adir.home': 'Startseite',
     2756    'adir.logout': 'Abmelden',
     2757    'adir.login': 'Anmelden',
     2758    'adir.title': 'Mitglieder',
     2759    'adir.count_one': '{n} Klonkt',
     2760    'adir.count_many': "{n} Klonkt's",
     2761    'adir.search_ph': 'Nach Namen suchen…',
     2762    'adir.search_aria': 'Mitglieder suchen',
     2763    'adir.search_btn': 'Suchen',
     2764    'adir.clear': 'Löschen',
     2765    'adir.empty_q': 'Keine Mitglieder gefunden für „{q}".',
     2766    'adir.empty': 'Noch keine Mitglieder.',
     2767    'adir.posts_one': '{n} Beitrag',
     2768    'adir.posts_many': '{n} Beiträge',
     2769    'adir.pager_aria': 'Seitennummerierung',
     2770    'adir.prev': '← Zurück',
     2771    'adir.page_info': 'Seite {page} von {pages}',
     2772    'adir.next': 'Weiter →',
     2773    'glog.admin_title': 'Dies ist ein Administrator-Konto',
     2774    'glog.admin_body': 'Du hast dich mit Google angemeldet, aber diese E-Mail-Adresse gehört dem Administrator dieser Seite. Die Google-Anmeldung ist nur für Hörer/Fans — Administratoren verwenden immer ihr Passwort (so kann eine Google-Anmeldung niemals versehentlich Administratorrechte erteilen).',
     2775    'glog.admin_li1': '<strong>Bist du der Administrator?</strong> <a href="/auth/admin">Melde dich mit Benutzername + Passwort an</a>.',
     2776    'glog.admin_li2': '<strong>Möchtest du dich künftig mit Google anmelden?</strong> Melde dich zuerst mit Passwort an und <strong>verknüpfe dein Google-Konto</strong> unter Konto → danach funktioniert die Google-Anmeldung auch für dich.',
     2777    'glog.admin_li3': '<strong>Möchtest du dich als Fan anmelden?</strong> Verwende ein <strong>anderes Google-Konto</strong> (nicht deine Administrator-Adresse).',
     2778    'glog.session_title': 'Anmeldung abgebrochen',
     2779    'glog.session_body': 'Die Google-Anmeldung wurde abgebrochen oder die Sitzung war abgelaufen. Versuche es einfach erneut.',
     2780    'glog.email_title': 'Keine verifizierte E-Mail-Adresse',
     2781    'glog.email_body': 'Dein Google-Konto hat keine verifizierte E-Mail-Adresse. Verifiziere deine Adresse bei Google.',
     2782    'glog.linked_title': 'Adresse bereits verknüpft',
     2783    'glog.linked_body': 'Diese E-Mail-Adresse ist bereits mit einem anderen Google-Konto verknüpft. Verwende dieses Konto oder wende dich an den Administrator.',
     2784    'glog.unavailable_title': 'Google-Anmeldung nicht verfügbar',
     2785    'glog.unavailable_body': 'Die Google-Anmeldung wurde auf dieser Seite (noch) nicht eingerichtet.',
     2786    'glog.failed_title': 'Google-Anmeldung fehlgeschlagen',
     2787    'glog.failed_body': 'Beim Anmelden mit Google ist etwas schiefgelaufen. Bitte versuche es erneut.',
     2788    'pusr.post_one': 'Beitrag',
     2789    'pusr.post_many': 'Beiträge',
     2790    'pusr.on_this_site': 'auf dieser Seite',
     2791    'pusr.total': '{n} insgesamt',
     2792    'pusr.joined': 'dabei seit {date}',
     2793    'pusr.send_dm': 'DM senden',
     2794    'pusr.empty': 'Noch keine Beiträge auf dieser Seite.',
     2795    'pusr.posts_heading': 'Beiträge',
     2796    'pusr.untitled': '(ohne Titel)',
     2797    'phub.main_badge': 'Hauptseite',
     2798    'phub.view_page': 'Seite ansehen',
     2799    'phub.members': 'Mitglieder',
     2800    'phub.post_one': 'Beitrag',
     2801    'phub.post_many': 'Beiträge',
     2802    'phub.all_members': 'Alle {n} Mitglieder',
     2803    'phub.latest_posts': 'Neueste Beiträge unserer Mitglieder.',
     2804    'cfeed.title': 'Zirkel',
     2805    'cfeed.sub': 'Was bei den anderen Seiten in meinem Zirkel passiert.',
     2806    'cfeed.count': '{n} Seiten in meinem Zirkel',
     2807    'cfeed.empty': 'Noch nichts in meinem Zirkel.',
     2808    'cfeed.grid_view': 'Rasteransicht',
     2809    'cpost.back': 'Zirkel',
     2810    'cpost.via': 'via',
     2811    'cpost.read_more': 'Weiterlesen bei {source}',
     2812    'vblk.title': 'Betrachter-Modus',
     2813    'vblk.text_before': 'Dies ist ein schreibgeschütztes Konto. Du kannst alles ansehen, aber',
     2814    'vblk.text_strong': 'nichts ändern',
     2815    'vblk.text_after': '— Speichern, Hochladen, Löschen und Kommentieren sind deaktiviert.',
     2816    'vblk.back': 'Zurück',
     2817    'vblk.to_home': 'Zur Hauptseite',
     2818    'phome.empty_title': 'Hier ist es noch still.',
     2819    'phome.empty_sub': 'Noch keine Beiträge. Spannend.',
     2820    'phome.write_first': 'Schreibe deinen ersten Beitrag',
     2821    'phome.grid_view': 'Rasteransicht',
     2822    'fav.title': 'Favoriten',
     2823    'fav.sub': 'Beiträge, die dir gefallen haben. Tippe auf das ♥ eines Beitrags, um ihn hier zu speichern.',
     2824    'fav.empty': 'Du hast noch keine Favoriten. Öffne einen Beitrag und tippe auf das Herz ♡.',
     2825    'chlog.back': 'Zurück',
     2826    'chlog.title': 'Änderungen',
     2827    'chlog.app_version': 'App-Version',
     2828    'chlog.fed_proto': 'Föderations-Proto',
     2829    'chlog.manage_updates': 'Updates verwalten',
     2830    'e404.title': 'Seite nicht gefunden',
     2831    'e404.sub': 'Diese Seite existiert nicht (mehr). Vielleicht ist der Link veraltet oder falsch eingegeben.',
     2832    'e404.home': 'Zur Startseite',
     2833    'e404.archive': 'Archiv',
     2834    'ptype.eyebrow': 'Typ',
     2835    'ptype.count_one': '{n} Beitrag',
     2836    'ptype.count_many': '{n} Beiträge',
     2837    'ptype.empty': 'Noch keine Beiträge dieses Typs.',
     2838    'ptype.untitled': '(ohne Titel)',
     2839    'ptag.eyebrow': 'Tag',
     2840    'ptag.count_one': '{n} Beitrag',
     2841    'ptag.count_many': '{n} Beiträge',
     2842    'ptag.empty': 'Noch keine Beiträge mit diesem Tag.',
     2843    'ptag.untitled': '(ohne Titel)',
     2844    'parch.back': 'Zurück',
     2845    'parch.title': 'Archiv',
     2846    'parch.count_one': '{n} Beitrag',
     2847    'parch.count_many': '{n} Beiträge',
     2848    'parch.empty': 'Noch keine Beiträge.',
     2849    'prin.tagline': 'Deine Direktnachrichten auf dieser Seite.',
     2850    'prin.empty': 'Noch keine Unterhaltungen. Öffne das Profil einer Person und klicke auf „DM senden“, um eine zu beginnen.',
     2851    'prin.empty_conv': 'Leere Unterhaltung',
     2852    'prcv.back_aria': 'Zurück zum Posteingang',
     2853    'prcv.inbox': 'Posteingang',
     2854    'prcv.unknown': 'Unbekannt',
     2855    'prcv.view_profile': 'Profil ansehen',
     2856    'prcv.placeholder': 'Nachricht…',
     2857    'prcv.send': 'Senden',
    19482858  },
    19492859};
  • src/views/pages/404.ejs

    rbbf9d1a r4885eab  
    11<section class="nf">
    22  <div class="nf-code">404</div>
    3   <h1 class="nf-title">Pagina niet gevonden</h1>
    4   <p class="nf-sub">Deze pagina bestaat niet (meer). Misschien is de link verouderd of verkeerd getypt.</p>
     3  <h1 class="nf-title"><%= t('e404.title') %></h1>
     4  <p class="nf-sub"><%= t('e404.sub') %></p>
    55  <div class="nf-actions">
    6     <a class="nf-btn nf-btn-primary" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/">← Naar de homepagina</a>
     6    <a class="nf-btn nf-btn-primary" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/">← <%= t('e404.home') %></a>
    77    <% if (typeof site !== 'undefined' && site && site.show_archive_link !== 0) { %>
    8       <a class="nf-btn" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/archive">Archief</a>
     8      <a class="nf-btn" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/archive"><%= t('e404.archive') %></a>
    99    <% } %>
    1010  </div>
  • src/views/pages/account.ejs

    rbbf9d1a r4885eab  
    22
    33  <header class="ax-header">
    4     <a href="/" class="ax-back" aria-label="Terug naar home">←</a>
     4    <a href="/" class="ax-back" aria-label="<%= t('acct.back_home') %>">←</a>
    55    <div>
    6       <h1>Account</h1>
    7       <p class="ax-tagline">Profiel en avatar.</p>
     6      <h1><%= t('acct.title') %></h1>
     7      <p class="ax-tagline"><%= t('acct.subtitle') %></p>
    88    </div>
    99  </header>
     
    1616      <span class="ax-viewer-ico" aria-hidden="true">👁️</span>
    1717      <div>
    18         <strong>Kijker-modus</strong>
    19         <span>Dit is een demo-account. Je kunt alles bekijken, maar niets wijzigen — ook geen foto of bio.</span>
     18        <strong><%= t('acct.viewer_mode') %></strong>
     19        <span><%= t('acct.viewer_note') %></span>
    2020      </div>
    2121    </div>
     
    2424  <%# ── PROFILE ────────────────────────────────────────────── %>
    2525  <section class="ax-card">
    26     <div class="ax-card-title">Profiel</div>
     26    <div class="ax-card-title"><%= t('acct.profile') %></div>
    2727
    2828    <div class="ax-profile-id">
    2929      <% if (canMutate) { %>
    3030        <form action="/account/avatar" method="post" enctype="multipart/form-data" class="ax-avatar-form">
    31           <label class="ax-avatar-wrap" title="Klik om je foto te wijzigen">
     31          <label class="ax-avatar-wrap" title="<%= t('acct.avatar_change') %>">
    3232            <span class="ax-profile-avatar">
    3333              <% if (account.avatar_url) { %>
     
    5656        <div class="ax-profile-role"><%= account.role %> · <%= account.email %></div>
    5757        <% if (account.created_at) { %>
    58           <div class="ax-profile-joined">Lid sinds <%= formatDate(account.created_at) %></div>
     58          <div class="ax-profile-joined"><%= t('acct.member_since') %> <%= formatDate(account.created_at) %></div>
    5959        <% } %>
    6060        <% if (account.avatar_url && canMutate) { %>
    6161          <form action="/account/avatar/remove" method="post" class="ax-avatar-remove">
    62             <button type="submit" class="ax-linkbtn">Foto verwijderen</button>
     62            <button type="submit" class="ax-linkbtn"><%= t('acct.avatar_remove') %></button>
    6363          </form>
    6464        <% } %>
     
    6969      <form action="/account/profile" method="post" class="ax-form ax-form-bio">
    7070        <label class="ax-field">
    71           <span>E-mailadres</span>
    72           <input type="email" name="email" value="<%= account.email || '' %>" maxlength="254" autocomplete="email" placeholder="jij@email.nl">
    73         </label>
    74         <label class="ax-field">
    75           <span>Bio</span>
    76           <textarea name="bio" rows="3" maxlength="500" placeholder="Een korte regel over jezelf"><%= account.bio || '' %></textarea>
     71          <span><%= t('acct.email') %></span>
     72          <input type="email" name="email" value="<%= account.email || '' %>" maxlength="254" autocomplete="email" placeholder="<%= t('acct.email_ph') %>">
     73        </label>
     74        <label class="ax-field">
     75          <span><%= t('acct.bio') %></span>
     76          <textarea name="bio" rows="3" maxlength="500" placeholder="<%= t('acct.bio_ph') %>"><%= account.bio || '' %></textarea>
    7777        </label>
    7878        <div class="ax-form-actions">
    79           <button type="submit" class="ax-btn ax-btn-primary">Opslaan</button>
     79          <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.save') %></button>
    8080        </div>
    8181      </form>
     
    8383      <div class="ax-form ax-form-bio">
    8484        <label class="ax-field">
    85           <span>Bio</span>
    86           <textarea rows="3" disabled placeholder="Geen bio"><%= account.bio || '' %></textarea>
     85          <span><%= t('acct.bio') %></span>
     86          <textarea rows="3" disabled placeholder="<%= t('acct.bio_empty') %>"><%= account.bio || '' %></textarea>
    8787        </label>
    8888      </div>
     
    9393  <% if (typeof editableSite !== 'undefined' && editableSite && canMutate) { %>
    9494  <section class="ax-card">
    95     <div class="ax-card-title">Site</div>
     95    <div class="ax-card-title"><%= t('acct.site') %></div>
    9696    <form action="/account/site" method="post" class="ax-form">
    9797      <label class="ax-field">
    98         <span>Site-naam <small>— getoond in de kop van je site</small></span>
     98        <span><%= t('acct.site_name') %> <small><%= t('acct.site_name_hint') %></small></span>
    9999        <input type="text" name="site_title" value="<%= editableSite.title || '' %>" maxlength="200" required>
    100100      </label>
    101101      <label class="ax-field">
    102         <span>Tagline <small>— korte oneliner (optioneel)</small></span>
     102        <span><%= t('acct.tagline') %> <small><%= t('acct.tagline_hint') %></small></span>
    103103        <input type="text" name="site_tagline" value="<%= editableSite.tagline || '' %>" maxlength="200">
    104104      </label>
    105105      <div class="ax-form-actions">
    106         <button type="submit" class="ax-btn ax-btn-primary">Site opslaan</button>
     106        <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.site_save') %></button>
    107107      </div>
    108108    </form>
     
    115115  <% if (hasPassword) { %>
    116116  <section class="ax-card">
    117     <div class="ax-card-title">Wachtwoord wijzigen</div>
     117    <div class="ax-card-title"><%= t('acct.password_change') %></div>
    118118    <form action="/account/password" method="post" class="ax-form">
    119119      <label class="ax-field">
    120         <span>Huidig wachtwoord</span>
     120        <span><%= t('acct.password_current') %></span>
    121121        <input type="password" name="current" required autocomplete="current-password">
    122122      </label>
    123123      <div class="ax-row ax-row-2">
    124124        <label class="ax-field">
    125           <span>Nieuw wachtwoord <small>(min 8 tekens)</small></span>
     125          <span><%= t('acct.password_new') %> <small><%= t('acct.password_min') %></small></span>
    126126          <input type="password" name="new_password" required minlength="8" autocomplete="new-password">
    127127        </label>
    128128        <label class="ax-field">
    129           <span>Bevestig nieuw wachtwoord</span>
     129          <span><%= t('acct.password_confirm') %></span>
    130130          <input type="password" name="confirm" required minlength="8" autocomplete="new-password">
    131131        </label>
    132132      </div>
    133133      <div class="ax-form-actions">
    134         <button type="submit" class="ax-btn ax-btn-primary">Wachtwoord wijzigen</button>
     134        <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.password_change') %></button>
    135135      </div>
    136136    </form>
     
    139139  <% if (typeof googleAvailable !== 'undefined' && googleAvailable) { %>
    140140  <section class="ax-card">
    141     <div class="ax-card-title">Inloggen met Google</div>
     141    <div class="ax-card-title"><%= t('acct.google_login') %></div>
    142142    <% if (typeof googleLinked !== 'undefined' && googleLinked) { %>
    143       <p class="ax-tagline" style="margin:0 0 1rem">✓ Je Google-account is gekoppeld — je kunt ook met Google inloggen.</p>
     143      <p class="ax-tagline" style="margin:0 0 1rem"><%= t('acct.google_linked') %></p>
    144144      <form action="/account/google/unlink" method="post">
    145         <button type="submit" class="ax-btn">Google ontkoppelen</button>
     145        <button type="submit" class="ax-btn"><%= t('acct.google_unlink') %></button>
    146146      </form>
    147147    <% } else { %>
    148       <p class="ax-tagline" style="margin:0 0 1rem">Koppel je Google-account zodat je voortaan ook met één klik via Google kunt inloggen (naast je wachtwoord).</p>
    149       <a href="/auth/google/link" class="ax-btn ax-btn-primary" data-full-load>Koppel Google-account</a>
     148      <p class="ax-tagline" style="margin:0 0 1rem"><%= t('acct.google_link_hint') %></p>
     149      <a href="/auth/google/link" class="ax-btn ax-btn-primary" data-full-load><%= t('acct.google_link') %></a>
    150150    <% } %>
    151151  </section>
     
    153153  <% } else { %>
    154154  <section class="ax-card">
    155     <div class="ax-card-title">Inloggen</div>
    156     <p class="ax-tagline" style="margin:0">Je bent ingelogd via Google (<%= account.email %>). Er is geen apart wachtwoord.</p>
     155    <div class="ax-card-title"><%= t('acct.login') %></div>
     156    <p class="ax-tagline" style="margin:0"><%= t('acct.login_google_only', { email: account.email }) %></p>
    157157  </section>
    158158  <% } %>
  • src/views/pages/archive.ejs

    rbbf9d1a r4885eab  
    99    <a href="<%= _archBase %>/" class="btn archive-back"
    1010       hx-get="<%= _archBase %>/?partial=1" hx-target="#pcms-main"
    11        hx-push-url="<%= _archBase %>/" hx-indicator="#pcms-loading">&larr; Terug</a>
     11       hx-push-url="<%= _archBase %>/" hx-indicator="#pcms-loading">&larr; <%= t('parch.back') %></a>
    1212  </p>
    13   <h1>Archief</h1>
    14   <p class="archive-meta"><%= totalPosts %> bericht<%= totalPosts !== 1 ? 'en' : '' %></p>
     13  <h1><%= t('parch.title') %></h1>
     14  <p class="archive-meta"><%= totalPosts === 1 ? t('parch.count_one', { n: totalPosts }) : t('parch.count_many', { n: totalPosts }) %></p>
    1515
    1616  <% if (totalPosts === 0) { %>
    17     <p class="empty-state">Nog geen berichten.</p>
     17    <p class="empty-state"><%= t('parch.empty') %></p>
    1818  <% } %>
    1919
  • src/views/pages/artists-directory.ejs

    rbbf9d1a r4885eab  
    1515  <nav class="adir-nav">
    1616    <div class="adir-nav-inner container">
    17       <a class="adir-nav-home" href="/"><span aria-hidden="true">←</span> <%= (typeof hubTitle !== 'undefined' && hubTitle) ? hubTitle : 'Home' %></a>
     17      <a class="adir-nav-home" href="/"><span aria-hidden="true">←</span> <%= (typeof hubTitle !== 'undefined' && hubTitle) ? hubTitle : t('adir.home') %></a>
    1818      <span class="adir-nav-spacer"></span>
    1919      <% if (user) { %>
    2020        <a class="adir-nav-link" href="/account"><%= user.username %></a>
    21         <a class="adir-nav-link" href="/auth/logout">Uitloggen</a>
     21        <a class="adir-nav-link" href="/auth/logout"><%= t('adir.logout') %></a>
    2222      <% } else { %>
    23         <a class="adir-nav-link" href="/auth/login">Inloggen</a>
     23        <a class="adir-nav-link" href="/auth/login"><%= t('adir.login') %></a>
    2424      <% } %>
    2525    </div>
     
    2727
    2828  <header class="adir-head container">
    29     <h1 class="adir-title">Leden</h1>
    30     <p class="adir-sub"><%= total %> Klonkt<%= total === 1 ? '' : "'s" %></p>
     29    <h1 class="adir-title"><%= t('adir.title') %></h1>
     30    <p class="adir-sub"><%= t(total === 1 ? 'adir.count_one' : 'adir.count_many', { n: total }) %></p>
    3131
    3232    <form class="adir-search" method="get" action="/leden" role="search">
    33       <input type="search" name="q" value="<%= q %>" placeholder="Zoek op naam…"
    34              autocomplete="off" aria-label="Zoek leden">
    35       <button type="submit" class="adir-search-btn" aria-label="Zoeken">
     33      <input type="search" name="q" value="<%= q %>" placeholder="<%= t('adir.search_ph') %>"
     34             autocomplete="off" aria-label="<%= t('adir.search_aria') %>">
     35      <button type="submit" class="adir-search-btn" aria-label="<%= t('adir.search_btn') %>">
    3636        <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
    3737      </button>
    38       <% if (q) { %><a class="adir-search-clear" href="/leden">Wissen</a><% } %>
     38      <% if (q) { %><a class="adir-search-clear" href="/leden"><%= t('adir.clear') %></a><% } %>
    3939    </form>
    4040  </header>
     
    4343    <% if (!artists.length) { %>
    4444      <p class="adir-empty">
    45         <% if (q) { %>Geen leden gevonden voor "<strong><%= q %></strong>".<% } else { %>Nog geen leden.<% } %>
     45        <% if (q) { %><%- t('adir.empty_q', { q: '<strong>' + q + '</strong>' }) %><% } else { %><%= t('adir.empty') %><% } %>
    4646      </p>
    4747    <% } else { %>
     
    5555            <span class="roster-name"><%= a.title %></span>
    5656            <% if (a.tagline) { %><span class="roster-tag"><%= a.tagline %></span><% } %>
    57             <span class="roster-count"><%= a.post_count %> post<%= a.post_count === 1 ? '' : 's' %></span>
     57            <span class="roster-count"><%= t(a.post_count === 1 ? 'adir.posts_one' : 'adir.posts_many', { n: a.post_count }) %></span>
    5858          </a>
    5959        <% }); %>
     
    6161
    6262      <% if (pages > 1) { %>
    63         <nav class="adir-pager" aria-label="Paginering">
     63        <nav class="adir-pager" aria-label="<%= t('adir.pager_aria') %>">
    6464          <% if (page > 1) { %>
    65             <a class="adir-page" href="<%= _qs({ page: page - 1 }) %>" rel="prev">← Vorige</a>
     65            <a class="adir-page" href="<%= _qs({ page: page - 1 }) %>" rel="prev"><%= t('adir.prev') %></a>
    6666          <% } else { %>
    67             <span class="adir-page is-disabled">← Vorige</span>
     67            <span class="adir-page is-disabled"><%= t('adir.prev') %></span>
    6868          <% } %>
    69           <span class="adir-page-info">Pagina <%= page %> van <%= pages %></span>
     69          <span class="adir-page-info"><%= t('adir.page_info', { page: page, pages: pages }) %></span>
    7070          <% if (page < pages) { %>
    71             <a class="adir-page" href="<%= _qs({ page: page + 1 }) %>" rel="next">Volgende →</a>
     71            <a class="adir-page" href="<%= _qs({ page: page + 1 }) %>" rel="next"><%= t('adir.next') %></a>
    7272          <% } else { %>
    73             <span class="adir-page is-disabled">Volgende →</span>
     73            <span class="adir-page is-disabled"><%= t('adir.next') %></span>
    7474          <% } %>
    7575        </nav>
  • src/views/pages/auth-login.ejs

    rbbf9d1a r4885eab  
    1111  <%
    1212    var GMAP = {
    13       admin: { icon: '🔐', title: 'Dit is een beheerders-account',
    14         body: 'Je logde in met Google, maar dit e-mailadres hoort bij de beheerder van deze site. Inloggen met Google is alleen voor luisteraars/fans — beheerders gebruiken altijd hun wachtwoord (zo kan een Google-login nooit per ongeluk beheerrechten geven).',
    15         list: ['<strong>Ben je de beheerder?</strong> <a href="/auth/admin">Log in met je gebruikersnaam + wachtwoord</a>.', '<strong>Wil je voortaan met Google inloggen?</strong> Log eerst in met wachtwoord en <strong>koppel je Google-account</strong> onder Account → daarna werkt Google-login ook voor jou.', '<strong>Wil je als fan inloggen?</strong> Gebruik een <strong>ander Google-account</strong> (niet je beheerdersadres).'] },
    16       session: { icon: '⏱️', title: 'Login afgebroken', body: 'De Google-login is afgebroken of de sessie was verlopen. Probeer het gewoon opnieuw.' },
    17       email: { icon: '✉️', title: 'Geen geverifieerd e-mailadres', body: 'Je Google-account heeft geen geverifieerd e-mailadres. Verifieer je adres bij Google.' },
    18       linked: { icon: '🔗', title: 'Adres al gekoppeld', body: 'Dit e-mailadres is al aan een ander Google-account gekoppeld. Gebruik dat account, of neem contact op met de beheerder.' },
    19       unavailable: { icon: '🚫', title: 'Google-login niet beschikbaar', body: 'Inloggen met Google is op deze site (nog) niet ingesteld.' },
    20       failed: { icon: '⚠️', title: 'Google-login mislukt', body: 'Er ging iets mis bij het inloggen met Google. Probeer het opnieuw.' },
     13      admin: { icon: '🔐', title: t('glog.admin_title'),
     14        body: t('glog.admin_body'),
     15        list: [t('glog.admin_li1'), t('glog.admin_li2'), t('glog.admin_li3')] },
     16      session: { icon: '⏱️', title: t('glog.session_title'), body: t('glog.session_body') },
     17      email: { icon: '✉️', title: t('glog.email_title'), body: t('glog.email_body') },
     18      linked: { icon: '🔗', title: t('glog.linked_title'), body: t('glog.linked_body') },
     19      unavailable: { icon: '🚫', title: t('glog.unavailable_title'), body: t('glog.unavailable_body') },
     20      failed: { icon: '⚠️', title: t('glog.failed_title'), body: t('glog.failed_body') },
    2121    };
    2222    var _g = (typeof gerr !== 'undefined' && gerr && GMAP[gerr]) ? GMAP[gerr] : null;
  • src/views/pages/auth-reset-request.ejs

    rbbf9d1a r4885eab  
    11<div class="container auth-page">
    2   <h1>Wachtwoord resetten</h1>
     2  <h1><%= t('arrq.title') %></h1>
    33
    44  <% if (sent) { %>
    55    <div class="alert alert-success">
    6       Als er een account bestaat voor dat e-mailadres, is er een reset-link verstuurd.
     6      <%= t('arrq.sent') %>
    77    </div>
    88    <% if (devResetUrl) { %>
    99      <div class="alert alert-info">
    10         <strong>Geen e-mailserver ingesteld</strong> — reset-link hieronder.
     10        <%- t('arrq.no_mailserver') %>
    1111        <p style="margin: 0.5rem 0 0; word-break: break-all;">
    1212          <a href="<%= devResetUrl %>"><%= devResetUrl %></a>
     
    1515    <% } %>
    1616    <% if (typeof mailer !== 'undefined' && !mailer) { %>
    17       <p class="auth-link-muted">Geen e-mail ingesteld? De beheerder kan ook <code>npm run reset-admin</code> draaien op de server.</p>
     17      <p class="auth-link-muted"><%- t('arrq.no_mail_cli') %></p>
    1818    <% } %>
    19     <p class="auth-link"><a href="/auth/login">&larr; Terug naar inloggen</a></p>
     19    <p class="auth-link"><a href="/auth/login"><%= t('arrq.back_login') %></a></p>
    2020  <% } else { %>
    21     <p class="auth-tagline">Vul je e-mail in; we sturen een reset-link.</p>
     21    <p class="auth-tagline"><%= t('arrq.tagline') %></p>
    2222
    2323    <% if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
     
    2525    <form method="post" action="/auth/reset-request" class="auth-form">
    2626      <label>
    27         <span>E-mail</span>
     27        <span><%= t('arrq.email') %></span>
    2828        <input type="email" name="email" required autofocus autocomplete="email">
    2929      </label>
    30       <button type="submit" class="btn btn-primary">Stuur reset-link</button>
    31       <p class="auth-link"><a href="/auth/login">&larr; Terug naar inloggen</a></p>
     30      <button type="submit" class="btn btn-primary"><%= t('arrq.submit') %></button>
     31      <p class="auth-link"><a href="/auth/login"><%= t('arrq.back_login') %></a></p>
    3232    </form>
    3333  <% } %>
  • src/views/pages/auth-reset.ejs

    rbbf9d1a r4885eab  
    11<div class="container auth-page">
    2   <h1>Wachtwoord resetten</h1>
     2  <h1><%= t('arst.title') %></h1>
    33
    44  <% if (!token) { %>
    55    <% if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
    66    <p class="auth-link">
    7       <a href="/auth/reset-request">Vraag een nieuwe reset-link aan</a>
     7      <a href="/auth/reset-request"><%= t('arst.request_new') %></a>
    88      &middot;
    9       <a href="/auth/login">Terug naar inloggen</a>
     9      <a href="/auth/login"><%= t('arst.back_login') %></a>
    1010    </p>
    1111  <% } else { %>
    12     <p class="auth-tagline">Stel een nieuw wachtwoord in voor <strong><%= username %></strong>.</p>
     12    <p class="auth-tagline"><%- t('arst.set_for', { username: '<strong>' + username + '</strong>' }) %></p>
     13    <%# arst.set_for bevat {username} → rendered met <%- omdat username in <strong> staat %>
    1314
    1415    <% if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
     
    1617    <form method="post" action="/auth/reset/<%= token %>" class="auth-form">
    1718      <label>
    18         <span>Nieuw wachtwoord (min 8 tekens)</span>
     19        <span><%= t('arst.new_pw') %></span>
    1920        <input type="password" name="new_password" required minlength="8" autofocus autocomplete="new-password">
    2021      </label>
    2122      <label>
    22         <span>Bevestig nieuw wachtwoord</span>
     23        <span><%= t('arst.confirm_pw') %></span>
    2324        <input type="password" name="confirm" required minlength="8" autocomplete="new-password">
    2425      </label>
    25       <button type="submit" class="btn btn-primary">Wachtwoord instellen</button>
     26      <button type="submit" class="btn btn-primary"><%= t('arst.submit') %></button>
    2627    </form>
    2728  <% } %>
  • src/views/pages/changelog.ejs

    rbbf9d1a r4885eab  
    11<div class="container changelog-page">
    2   <p><a href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/" class="btn">&larr; Terug</a></p>
    3   <h1>Wijzigingen</h1>
     2  <p><a href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/" class="btn">&larr; <%= t('chlog.back') %></a></p>
     3  <h1><%= t('chlog.title') %></h1>
    44  <p class="cl-meta">
    5     App-versie <strong>v<%= appVersion %></strong>
     5    <%= t('chlog.app_version') %> <strong>v<%= appVersion %></strong>
    66    <span class="cl-sep">·</span>
    7     Federatie-proto <strong><%= proto %></strong>
     7    <%= t('chlog.fed_proto') %> <strong><%= proto %></strong>
    88    <% if (typeof user !== 'undefined' && user && user.role === 'god') { %>
    9       <span class="cl-sep">·</span> <a href="/admin/updates">Updates beheren</a>
     9      <span class="cl-sep">·</span> <a href="/admin/updates"><%= t('chlog.manage_updates') %></a>
    1010    <% } %>
    1111  </p>
  • src/views/pages/circle-feed.ejs

    rbbf9d1a r4885eab  
    1010    </svg>
    1111  </span>
    12   <h1>Cirkel</h1>
    13   <p class="cirkel-sub">Wat er speelt bij de andere sites in mijn cirkel.</p>
     12  <h1><%= t('cfeed.title') %></h1>
     13  <p class="cirkel-sub"><%= t('cfeed.sub') %></p>
    1414  <% if (typeof sites !== 'undefined' && sites && sites.length) { %>
    1515    <% if (sites.length <= 5) { %>
     
    2424    <% } else { %>
    2525      <%# Meer dan 5: geen chips (zou een muur worden) — alleen het aantal. %>
    26       <span class="cirkel-count"><%= sites.length %> sites in mijn cirkel</span>
     26      <span class="cirkel-count"><%= t('cfeed.count', { n: sites.length }) %></span>
    2727    <% } %>
    2828  <% } %>
     
    3131<% if (!posts.length) { %>
    3232  <div class="container">
    33     <p class="cirkel-empty">Nog niets in mijn cirkel.</p>
     33    <p class="cirkel-empty"><%= t('cfeed.empty') %></p>
    3434  </div>
    3535<% } else { %>
     
    5050
    5151  <!-- ===== GRID (body[data-feed-view="grid"]) ===== -->
    52   <section class="feed-grid container" aria-label="Grid-weergave">
     52  <section class="feed-grid container" aria-label="<%= t('cfeed.grid_view') %>">
    5353    <div class="grid-tiles">
    5454      <% posts.forEach(function(post){ %>
  • src/views/pages/circle-post.ejs

    rbbf9d1a r4885eab  
    22  <p class="cirkel-post-back">
    33    <a href="/cirkel" hx-get="/cirkel?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
    4        hx-push-url="/cirkel" hx-indicator="#pcms-loading">&larr; Cirkel</a>
     4       hx-push-url="/cirkel" hx-indicator="#pcms-loading">&larr; <%= t('cpost.back') %></a>
    55  </p>
    66
     
    88    <div class="cirkel-post-src">
    99      <% if (post.sourceAvatar) { %><img class="cirkel-post-avatar" src="<%= post.sourceAvatar %>" alt=""><% } %>
    10       <span>via
     10      <span><%= t('cpost.via') %>
    1111        <% if (post.sourceUrl) { %><a href="<%= post.sourceUrl %>" target="_blank" rel="noopener"><%= post.sourceName %></a>
    1212        <% } else { %><%= post.sourceName %><% } %>
     
    3737  <% if (post.originalUrl) { %>
    3838    <p class="cirkel-post-readmore">
    39       <a href="<%= post.originalUrl %>" target="_blank" rel="noopener">Lees verder bij <%= post.sourceName %> &rarr;</a>
     39      <a href="<%= post.originalUrl %>" target="_blank" rel="noopener"><%= t('cpost.read_more', { source: post.sourceName }) %> &rarr;</a>
    4040    </p>
    4141  <% } %>
  • src/views/pages/download.ejs

    rbbf9d1a r4885eab  
     1<%
     2  var _ready_h1     = t('dl.ready_title');
     3  var _ready_sub    = t('dl.ready_sub');
     4  var _manual       = t('dl.manual');
     5  var _capture_sub  = t('dl.capture_sub');
     6  var _email_ph     = t('dl.email_ph');
     7  var _download_btn = t('dl.download_btn');
     8%>
    19<%
    210  var t = (typeof dlTrack !== 'undefined') ? dlTrack : {};
     
    1523
    1624    <% if (st === 'ready') { %>
    17       <h1 class="dl-h1">Bedankt! ⬇</h1>
    18       <p class="dl-sub">Je download zou nu moeten starten. Gebeurt er niets?</p>
    19       <p><a class="dl-go" href="<%= fileUrl %>">Download handmatig starten</a></p>
     25      <h1 class="dl-h1"><%= _ready_h1 %></h1>
     26      <p class="dl-sub"><%= _ready_sub %></p>
     27      <p><a class="dl-go" href="<%= fileUrl %>"><%= _manual %></a></p>
    2028      <script>
    2129        // Auto-start de download (zelfde-origin attachment-link).
     
    2331      </script>
    2432    <% } else { %>
    25       <h1 class="dl-h1">Download <%= t.title %></h1>
    26       <p class="dl-sub">Laat je e-mailadres achter en je krijgt het bestand. Je komt dan ook op de nieuwsbrieflijst — uitschrijven kan altijd.</p>
     33      <h1 class="dl-h1"><%= _download_btn %> <%= t.title %></h1>
     34      <p class="dl-sub"><%= _capture_sub %></p>
    2735      <% if (typeof dlError !== 'undefined' && dlError) { %><p class="dl-err"><%= dlError %></p><% } %>
    2836      <form method="POST" action="<%= siteUrlBase %>/download/<%= t.id %>" class="dl-form">
    29         <input type="email" name="email" required placeholder="jouw@email.nl" value="<%= (typeof dlPrefill!=='undefined')?dlPrefill:'' %>" autocomplete="email">
    30         <button type="submit" class="dl-go">⬇ Download</button>
     37        <input type="email" name="email" required placeholder="<%= _email_ph %>" value="<%= (typeof dlPrefill!=='undefined')?dlPrefill:'' %>" autocomplete="email">
     38        <button type="submit" class="dl-go">⬇ <%= _download_btn %></button>
    3139      </form>
    3240    <% } %>
  • src/views/pages/epk.ejs

    rbbf9d1a r4885eab  
    1111    <% if (_photo) { %><div class="epk-photo"><img src="<%= _photo %>" alt="<%= site.title %>"></div><% } %>
    1212    <div class="epk-hero-text">
    13       <div class="epk-kicker">Perskit<% if (typeof user !== 'undefined' && user && user.role === 'god') { %> <a class="epk-edit-link" href="/admin/epk">✎ Bewerken</a><% } %></div>
     13      <div class="epk-kicker"><%= t('epk.kicker') %><% if (typeof user !== 'undefined' && user && user.role === 'god') { %> <a class="epk-edit-link" href="/admin/epk">✎ <%= t('epk.edit') %></a><% } %></div>
    1414      <h1 class="epk-title"><%= site.title %></h1>
    1515      <% if (epkBio) { %><p class="epk-bio"><%= epkBio %></p><% } %>
    1616      <div class="epk-actions">
    1717        <% if (_isMail) { %>
    18           <a class="epk-btn epk-btn-primary" href="mailto:<%= epkContact %>">✉ Contact / boeking</a>
     18          <a class="epk-btn epk-btn-primary" href="mailto:<%= epkContact %>">✉ <%= t('epk.contact_booking') %></a>
    1919        <% } else if (_isUrl) { %>
    20           <a class="epk-btn epk-btn-primary" href="<%= epkContact %>" target="_blank" rel="noopener">Contact / boeking</a>
     20          <a class="epk-btn epk-btn-primary" href="<%= epkContact %>" target="_blank" rel="noopener"><%= t('epk.contact_booking') %></a>
    2121        <% } %>
    22         <a class="epk-btn" href="<%= siteUrlBase %>/">Bekijk de site →</a>
    23         <% if (_photo) { %><a class="epk-btn" href="<%= _photo %>" download>⬇ Persfoto</a><% } %>
     22        <a class="epk-btn" href="<%= siteUrlBase %>/"><%= t('epk.view_site') %></a>
     23        <% if (_photo) { %><a class="epk-btn" href="<%= _photo %>" download>⬇ <%= t('epk.press_photo') %></a><% } %>
    2424      </div>
    2525    </div>
     
    2828  <% if (epkTracks && epkTracks.length) { %>
    2929    <div class="epk-block">
    30       <h2 class="epk-h2">Meest beluisterd</h2>
     30      <h2 class="epk-h2"><%= t('epk.most_played') %></h2>
    3131      <ul class="epk-tracks">
    3232        <% epkTracks.forEach(function(t, i){ %>
     
    4747  <% if (epkPosts && epkPosts.length) { %>
    4848    <div class="epk-block">
    49       <h2 class="epk-h2">Recent</h2>
     49      <h2 class="epk-h2"><%= t('epk.recent') %></h2>
    5050      <ul class="epk-posts">
    5151        <% epkPosts.forEach(function(p){ %>
  • src/views/pages/fan-gate.ejs

    rbbf9d1a r4885eab  
    99  <div class="fg-card">
    1010    <div class="fg-lock">🔒</div>
    11     <h1 class="fg-h1">Alleen voor fans</h1>
     11    <h1 class="fg-h1"><%= t('fgate.title') %></h1>
    1212    <% if (typeof fgTitle !== 'undefined' && fgTitle) { %><p class="fg-which">"<%= fgTitle %>"</p><% } %>
    13     <p class="fg-sub">Dit is exclusief voor ingelogde fans. Log in om het te bekijken.</p>
    14     <p><a class="fg-btn" href="/auth/login?next=<%= encodeURIComponent(fgNext || '/') %>">Inloggen / aanmelden</a></p>
     13    <p class="fg-sub"><%= t('fgate.sub') %></p>
     14    <p><a class="fg-btn" href="/auth/login?next=<%= encodeURIComponent(fgNext || '/') %>"><%= t('fgate.login') %></a></p>
    1515  </div>
    1616</section>
  • src/views/pages/favorites.ejs

    rbbf9d1a r4885eab  
    11<div class="container favorites-page">
    2   <h1>Favorieten</h1>
    3   <p class="fav-sub">Posts die je hebt geliket. Tik op het ♥ bij een post om 'm hier te bewaren.</p>
     2  <h1><%= t('fav.title') %></h1>
     3  <p class="fav-sub"><%= t('fav.sub') %></p>
    44
    55  <% if (!posts || !posts.length) { %>
    6     <p class="fav-empty">Je hebt nog geen favorieten. Open een post en tik op het hartje ♡.</p>
     6    <p class="fav-empty"><%= t('fav.empty') %></p>
    77  <% } else { %>
    88    <ul class="post-list">
  • src/views/pages/home.ejs

    rbbf9d1a r4885eab  
    2323  <% if (allPosts.length === 0) { %>
    2424    <div class="empty">
    25       <h1>Hier is het nog stil.</h1>
    26       <p>Nog geen posts. Spannend.</p>
     25      <h1><%= t('phome.empty_title') %></h1>
     26      <p><%= t('phome.empty_sub') %></p>
    2727      <% if (user && canMutate && permissions.canCreatePost(user, site)) { %>
    2828        <p style="margin-top:2rem">
     
    3030             hx-get="<%= _base %>/posts/new?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
    3131             hx-push-url="<%= _base %>/posts/new" hx-indicator="#pcms-loading">
    32             Schrijf je eerste post
     32            <%= t('phome.write_first') %>
    3333          </a>
    3434        </p>
     
    4747<!-- ========== GRID (only visible when body[data-feed-view="grid"]) ========== -->
    4848<% if (allPosts.length > 0) { %>
    49 <section class="feed-grid container" aria-label="Grid-weergave">
     49<section class="feed-grid container" aria-label="<%= t('phome.grid_view') %>">
    5050  <div class="grid-tiles">
    5151    <% allPosts.forEach(function(post) { %>
  • src/views/pages/hub-home.ejs

    rbbf9d1a r4885eab  
    2828        </span>
    2929        <span class="hub-main-info">
    30           <span class="hub-main-badge">Hoofdpagina</span>
     30          <span class="hub-main-badge"><%= t('phub.main_badge') %></span>
    3131          <span class="hub-main-name"><%= mainSite.title %></span>
    3232          <% if (mainSite.tagline) { %><span class="hub-main-tag"><%= mainSite.tagline %></span><% } %>
    3333        </span>
    34         <span class="hub-main-cta">Bekijk de pagina <span aria-hidden="true">→</span></span>
     34        <span class="hub-main-cta"><%= t('phub.view_page') %> <span aria-hidden="true">→</span></span>
    3535      </a>
    3636    </section>
     
    4343    <section class="hub-sec">
    4444      <h2 class="hub-sec-title">
    45         Leden
     45        <%= t('phub.members') %>
    4646        <% if (_hasMore) { %><span class="hub-sec-count"><%= _total %></span><% } %>
    4747      </h2>
     
    5757            <span class="roster-name"><%= a.title %></span>
    5858            <% if (a.tagline) { %><span class="roster-tag"><%= a.tagline %></span><% } %>
    59             <span class="roster-count"><%= a.post_count %> post<%= a.post_count === 1 ? '' : 's' %></span>
     59            <span class="roster-count"><%= a.post_count %> <%= a.post_count === 1 ? t('phub.post_one') : t('phub.post_many') %></span>
    6060          </a>
    6161        <% }); %>
     
    6363      <% if (_hasMore) { %>
    6464        <div class="hub-more">
    65           <a class="hub-more-link" href="/leden">Alle <%= _total %> leden <span aria-hidden="true">→</span></a>
     65          <a class="hub-more-link" href="/leden"><%= t('phub.all_members', { n: _total }) %> <span aria-hidden="true">→</span></a>
    6666        </div>
    6767      <% } %>
     
    7272    <% if (posts && posts.length) { %>
    7373    <section class="hub-sec">
    74       <h2 class="hub-sec-title">Laatste posts van onze leden.</h2>
     74      <h2 class="hub-sec-title"><%= t('phub.latest_posts') %></h2>
    7575      <div class="hub-feed">
    7676        <% posts.forEach(function(p){ %>
  • src/views/pages/linkbio.ejs

    rbbf9d1a r4885eab  
    1313
    1414  <% if (!links.length) { %>
    15     <p class="lb-empty">Nog geen links ingesteld.</p>
     15    <p class="lb-empty"><%= t('lbio.empty') %></p>
    1616  <% } else { %>
    1717    <div class="lb-links">
     
    2525  <% } %>
    2626
    27   <p class="lb-foot"><a href="<%= siteUrlBase %>/">← naar de site</a></p>
     27  <p class="lb-foot"><a href="<%= siteUrlBase %>/">← <%= t('lbio.back_to_site') %></a></p>
    2828</section>
    2929
  • src/views/pages/my-site.ejs

    rbbf9d1a r4885eab  
    11<div class="container admin-page">
    2   <p><a href="/" class="btn">&larr; Overzicht</a></p>
    3   <h1>Mijn Klonkt Hub</h1>
     2  <p><a href="/" class="btn">&larr; <%= t('myst.overview') %></a></p>
     3  <h1><%= t('myst.title') %></h1>
    44  <p class="admin-tagline"><%= mySite.title %></p>
    55
    6   <nav class="admin-quick-links" aria-label="Beheer-snelkoppelingen">
    7     <a href="/user/<%= mySite.slug %>/posts/new" class="btn">✍️ Nieuwe post</a>
    8     <a href="/admin/sites/<%= mySite.slug %>/edit" class="btn">🎨 Uiterlijk</a>
    9     <a href="/user/<%= mySite.slug %>/admin/comments" class="btn">💬 Reacties</a>
    10     <a href="/user/<%= mySite.slug %>" class="btn">👁️ Bekijk mijn site</a>
    11     <a href="/account" class="btn">👤 Account</a>
     6  <nav class="admin-quick-links" aria-label="<%= t('myst.quick_links_aria') %>">
     7    <a href="/user/<%= mySite.slug %>/posts/new" class="btn">✍️ <%= t('myst.new_post') %></a>
     8    <a href="/admin/sites/<%= mySite.slug %>/edit" class="btn">🎨 <%= t('myst.appearance') %></a>
     9    <a href="/user/<%= mySite.slug %>/admin/comments" class="btn">💬 <%= t('myst.comments') %></a>
     10    <a href="/user/<%= mySite.slug %>" class="btn">👁️ <%= t('myst.view_site') %></a>
     11    <a href="/account" class="btn">👤 <%= t('myst.account') %></a>
    1212  </nav>
    1313
    1414  <div class="admin-stats">
    15     <div class="stat-card"><div class="stat-num"><%= mine.posts %></div><div class="stat-label">Posts</div></div>
    16     <div class="stat-card"><div class="stat-num"><%= mine.published %></div><div class="stat-label">Gepubliceerd</div></div>
     15    <div class="stat-card"><div class="stat-num"><%= mine.posts %></div><div class="stat-label"><%= t('myst.posts') %></div></div>
     16    <div class="stat-card"><div class="stat-num"><%= mine.published %></div><div class="stat-label"><%= t('myst.published') %></div></div>
    1717  </div>
    1818
    1919  <% if (typeof posts !== 'undefined' && posts && posts.length) { %>
    2020    <% var _drafts = posts.filter(function(p){ return p.isDraft; }).length; %>
    21     <h2 class="ms-posts-title">Posts<% if (_drafts) { %> <span class="pl-draftcount"><%= _drafts %> concept<%= _drafts === 1 ? '' : 'en' %></span><% } %></h2>
     21    <%
     22      var _lblBadge = t('myst.draft_badge');
     23      var _lblUntitled = t('myst.untitled');
     24      var _lblEdit = t('myst.edit');
     25      var _lblView = t('myst.view');
     26    %>
     27    <h2 class="ms-posts-title"><%= t('myst.posts') %><% if (_drafts) { %> <span class="pl-draftcount"><%= _drafts === 1 ? t('myst.draft_count_one', { n: _drafts }) : t('myst.draft_count_many', { n: _drafts }) %></span><% } %></h2>
    2228    <ul class="post-admin-list">
    2329      <% posts.forEach(function(p) { %>
    2430        <li class="pal-row<%= p.isDraft ? ' is-draft' : '' %>">
    2531          <a class="pal-title" href="<%= p.editUrl %>">
    26             <% if (p.isDraft) { %><span class="pal-badge">Concept</span><% } %>
    27             <span class="pal-name"><%= p.title || '(zonder titel)' %></span>
     32            <% if (p.isDraft) { %><span class="pal-badge"><%= _lblBadge %></span><% } %>
     33            <span class="pal-name"><%= p.title || _lblUntitled %></span>
    2834          </a>
    2935          <span class="pal-actions">
    30             <a class="pal-link" href="<%= p.editUrl %>">Bewerken</a>
    31             <% if (!p.isDraft) { %><a class="pal-link" href="<%= p.viewUrl %>">Bekijk</a><% } %>
     36            <a class="pal-link" href="<%= p.editUrl %>"><%= _lblEdit %></a>
     37            <% if (!p.isDraft) { %><a class="pal-link" href="<%= p.viewUrl %>"><%= _lblView %></a><% } %>
    3238          </span>
    3339        </li>
  • src/views/pages/newsletter.ejs

    rbbf9d1a r4885eab  
    11<%
    22  var _site = site || {};
    3   var _title = _site.title || 'deze artiest';
     3  var _title = _site.title || t('news.this_artist');
    44  var st = (typeof nlState !== 'undefined') ? nlState : 'form';
    55%>
     
    77  <div class="nl-card">
    88    <% if (st === 'form') { %>
    9       <h1 class="nl-h1">Blijf op de hoogte</h1>
    10       <p class="nl-sub">Schrijf je in voor de nieuwsbrief van <strong><%= _title %></strong> — nieuwe muziek, shows en updates, rechtstreeks in je inbox. Uitschrijven kan altijd met één klik.</p>
     9      <h1 class="nl-h1"><%= t('news.form_title') %></h1>
     10      <p class="nl-sub"><%= t('news.form_sub_before') %><strong><%= _title %></strong><%= t('news.form_sub_after') %></p>
    1111      <form method="POST" action="<%= siteUrlBase %>/nieuwsbrief" class="nl-form">
    12         <input type="email" name="email" required placeholder="jouw@email.nl" value="<%= (typeof nlPrefill !== 'undefined') ? nlPrefill : '' %>" autocomplete="email">
    13         <button type="submit" class="nl-btn">Inschrijven</button>
     12        <input type="email" name="email" required placeholder="<%= t('news.email_ph') %>" value="<%= (typeof nlPrefill !== 'undefined') ? nlPrefill : '' %>" autocomplete="email">
     13        <button type="submit" class="nl-btn"><%= t('news.subscribe') %></button>
    1414      </form>
    1515    <% } else if (st === 'check') { %>
    16       <h1 class="nl-h1">Bijna klaar ✉</h1>
    17       <p class="nl-sub">We hebben een bevestigingsmail gestuurd naar <strong><%= (typeof nlEmail!=='undefined')?nlEmail:'je adres' %></strong>. Klik op de link in die mail om je inschrijving te bevestigen.</p>
     16      <h1 class="nl-h1"><%= t('news.check_title') %></h1>
     17      <p class="nl-sub"><%= t('news.check_sub_before') %><strong><%= (typeof nlEmail!=='undefined')?nlEmail:t('news.your_address') %></strong><%= t('news.check_sub_after') %></p>
    1818    <% } else if (st === 'done') { %>
    19       <h1 class="nl-h1">Je bent ingeschreven ✓</h1>
    20       <p class="nl-sub">Bedankt — je staat op de lijst van <strong><%= _title %></strong>.</p>
     19      <h1 class="nl-h1"><%= t('news.done_title') %></h1>
     20      <p class="nl-sub"><%= t('news.done_sub_before') %><strong><%= _title %></strong><%= t('news.done_sub_after') %></p>
    2121    <% } else if (st === 'confirmed') { %>
    22       <h1 class="nl-h1">Inschrijving bevestigd ✓</h1>
    23       <p class="nl-sub">Top! Je ontvangt voortaan de nieuwsbrief van <strong><%= _title %></strong>.</p>
     22      <h1 class="nl-h1"><%= t('news.confirmed_title') %></h1>
     23      <p class="nl-sub"><%= t('news.confirmed_sub_before') %><strong><%= _title %></strong><%= t('news.confirmed_sub_after') %></p>
    2424    <% } else if (st === 'unsubbed') { %>
    25       <h1 class="nl-h1">Uitgeschreven</h1>
    26       <p class="nl-sub">Je bent afgemeld. Je ontvangt geen nieuwsbrieven meer. Van gedachten veranderd? Je kunt je altijd opnieuw inschrijven.</p>
     25      <h1 class="nl-h1"><%= t('news.unsubbed_title') %></h1>
     26      <p class="nl-sub"><%= t('news.unsubbed_sub') %></p>
    2727    <% } else if (st === 'invalid') { %>
    28       <h1 class="nl-h1">Ongeldig e-mailadres</h1>
    29       <p class="nl-sub">Controleer je adres en probeer 't opnieuw.</p>
    30       <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief">← Terug</a></p>
     28      <h1 class="nl-h1"><%= t('news.invalid_title') %></h1>
     29      <p class="nl-sub"><%= t('news.invalid_sub') %></p>
     30      <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief"><%= t('news.back') %></a></p>
    3131    <% } else if (st === 'smtperror') { %>
    32       <h1 class="nl-h1">Even niet gelukt</h1>
    33       <p class="nl-sub">De bevestigingsmail kon niet verstuurd worden. Probeer 't later nog eens.</p>
     32      <h1 class="nl-h1"><%= t('news.smtperror_title') %></h1>
     33      <p class="nl-sub"><%= t('news.smtperror_sub') %></p>
    3434    <% } else if (st === 'badtoken') { %>
    35       <h1 class="nl-h1">Link ongeldig of verlopen</h1>
    36       <p class="nl-sub">Deze link werkt niet meer. Schrijf je eventueel opnieuw in.</p>
    37       <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief">Naar inschrijven</a></p>
     35      <h1 class="nl-h1"><%= t('news.badtoken_title') %></h1>
     36      <p class="nl-sub"><%= t('news.badtoken_sub') %></p>
     37      <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief"><%= t('news.to_subscribe') %></a></p>
    3838    <% } else { %>
    39       <h1 class="nl-h1">Er ging iets mis</h1>
    40       <p class="nl-sub">Probeer 't later nog eens.</p>
     39      <h1 class="nl-h1"><%= t('news.error_title') %></h1>
     40      <p class="nl-sub"><%= t('news.error_sub') %></p>
    4141    <% } %>
    4242  </div>
  • src/views/pages/post-edit.ejs

    rbbf9d1a r4885eab  
    1515%>
    1616<div class="container post-edit-page">
    17   <h1><%= isNew ? 'Nieuwe post' : 'Post bewerken' %></h1>
     17  <h1><%= isNew ? t('pedit.title_new') : t('pedit.title_edit') %></h1>
    1818
    1919  <form method="post" action="<%= _base %><%= isNew ? '/posts/create' : '/posts/' + post.slug + '/save' %>" class="post-edit-form">
     
    2222    <section class="pe-card">
    2323      <label class="pe-field">
    24         <span>Titel</span>
     24        <span><%= t('pedit.f_title') %></span>
    2525        <input type="text" name="title" value="<%= post.title %>" required>
    2626      </label>
    2727      <div class="pe-row pe-row-2">
    2828        <label class="pe-field">
    29           <span>Slug (URL)</span>
    30           <input type="text" name="slug" value="<%= post.slug %>" placeholder="auto van titel als leeg">
     29          <span><%= t('pedit.f_slug') %></span>
     30          <input type="text" name="slug" value="<%= post.slug %>" placeholder="<%= t('pedit.slug_placeholder') %>">
    3131        </label>
    3232        <label class="pe-field">
    33           <span>Tags <small>(komma-gescheiden)</small></span>
     33          <span><%= t('pedit.f_tags') %> <small><%= t('pedit.tags_hint') %></small></span>
    3434          <input type="text" name="tags" value="<%= Array.isArray(post.tags) ? post.tags.join(', ') : '' %>">
    3535        </label>
    3636      </div>
    3737      <label class="pe-field">
    38         <span>Samenvatting &amp; Cirkel Preview</span>
     38        <span><%= t('pedit.f_excerpt') %></span>
    3939        <textarea name="excerpt" rows="2"><%= post.excerpt %></textarea>
    40         <small class="pe-hint" style="opacity:.7">Wordt ook gebruikt als samenvatting in <strong>Cirkels</strong> (andere sites die je post tonen). Leeg laten = begin van de post.</small>
     40        <small class="pe-hint" style="opacity:.7"><%- t('pedit.excerpt_hint') %></small>
    4141      </label>
    4242    </section>
     
    4444    <%# ── COVER ────────────────────────────────────────────────── %>
    4545    <section class="pe-card">
    46       <div class="pe-section-title">Cover</div>
     46      <div class="pe-section-title"><%= t('pedit.s_cover') %></div>
    4747      <div class="pe-cover-row">
    4848        <div class="pe-cover-thumb" id="cover-preview-wrap" <%= _hasCover ? '' : 'data-empty' %>>
     
    5252        <div class="pe-cover-actions">
    5353          <label class="pe-field">
    54             <span>Cover URL</span>
     54            <span><%= t('pedit.f_cover_url') %></span>
    5555            <input type="text" name="cover_image_url" id="cover-url-field"
    5656                   value="<%= post.cover_image_url || '' %>"
    5757                   inputmode="url" autocapitalize="none" spellcheck="false"
    58                    placeholder="/media/…  of https://…  (of upload met de knop)">
     58                   placeholder="<%= t('pedit.cover_url_placeholder') %>">
    5959          </label>
    6060          <div class="pe-cover-upload">
    6161            <button type="button" class="pe-btn pe-btn-secondary" id="cover-upload-trigger">
    62               📷 Upload nieuwe cover
     62              📷 <%= t('pedit.cover_upload_btn') %>
    6363            </button>
    6464            <input type="file" id="cover-upload-field" accept="image/jpeg,image/png,image/webp,image/gif" hidden>
     
    7272    <section class="pe-card pe-card-content">
    7373      <div class="pe-section-title">
    74         Content
    75         <small class="pe-content-hint">Sleep een afbeelding om in te voegen · selecteer tekst om op te maken</small>
     74        <%= t('pedit.s_content') %>
     75        <small class="pe-content-hint"><%= t('pedit.content_hint') %></small>
    7676      </div>
    7777      <div class="pe-editor-frame">
    7878        <div class="pe-toolbar" id="pe-toolbar" role="toolbar" aria-label="Format">
    79           <button type="button" id="pe-fs-done" class="pe-fs-done" title="Klaar met bewerken">✓ Klaar</button>
    80           <button type="button" data-cmd="bold" title="Vet (Ctrl+B)" aria-label="Vet">
     79          <button type="button" id="pe-fs-done" class="pe-fs-done" title="<%= t('pedit.tb_done_title') %>">✓ <%= t('pedit.tb_done') %></button>
     80          <button type="button" data-cmd="bold" title="<%= t('pedit.tb_bold_title') %>" aria-label="<%= t('pedit.tb_bold') %>">
    8181            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 4h8a4 4 0 0 1 0 8H6z"/><path d="M6 12h9a4 4 0 0 1 0 8H6z"/></svg>
    8282          </button>
    83           <button type="button" data-cmd="italic" title="Cursief (Ctrl+I)" aria-label="Cursief">
     83          <button type="button" data-cmd="italic" title="<%= t('pedit.tb_italic_title') %>" aria-label="<%= t('pedit.tb_italic') %>">
    8484            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg>
    8585          </button>
    86           <button type="button" data-cmd="underline" title="Onderstreept" aria-label="Onderstreept">
     86          <button type="button" data-cmd="underline" title="<%= t('pedit.tb_underline') %>" aria-label="<%= t('pedit.tb_underline') %>">
    8787            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="4" y1="20" x2="20" y2="20"/></svg>
    8888          </button>
    8989          <span class="pe-toolbar-sep" aria-hidden="true"></span>
    90           <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="h2" title="Kop">H2</button>
    91           <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="h3" title="Subkop">H3</button>
    92           <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="p"  title="Paragraaf">¶</button>
     90          <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="h2" title="<%= t('pedit.tb_h2') %>">H2</button>
     91          <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="h3" title="<%= t('pedit.tb_h3') %>">H3</button>
     92          <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="p"  title="<%= t('pedit.tb_p') %>">¶</button>
    9393          <span class="pe-toolbar-sep" aria-hidden="true"></span>
    94           <button type="button" data-cmd="insertUnorderedList" title="Lijst" aria-label="Lijst">
     94          <button type="button" data-cmd="insertUnorderedList" title="<%= t('pedit.tb_ul') %>" aria-label="<%= t('pedit.tb_ul') %>">
    9595            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4.5" cy="6" r="1.2"/><circle cx="4.5" cy="12" r="1.2"/><circle cx="4.5" cy="18" r="1.2"/></svg>
    9696          </button>
    97           <button type="button" data-cmd="insertOrderedList" title="Genummerde lijst" aria-label="Genummerde lijst">
     97          <button type="button" data-cmd="insertOrderedList" title="<%= t('pedit.tb_ol') %>" aria-label="<%= t('pedit.tb_ol') %>">
    9898            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/></svg>
    9999          </button>
    100           <button type="button" data-cmd="formatBlock" data-arg="blockquote" title="Quote" aria-label="Quote">
     100          <button type="button" data-cmd="formatBlock" data-arg="blockquote" title="<%= t('pedit.tb_quote') %>" aria-label="<%= t('pedit.tb_quote') %>">
    101101            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2H4c-1.25 0-2 .75-2 2v6c0 1.25.75 2 2 2h2.5C6 17.5 4 19 3 19v2z"/><path d="M14 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2h-4c-1.25 0-2 .75-2 2v6c0 1.25.75 2 2 2h2.5c-.5 4.5-2.5 6-3.5 6v2z"/></svg>
    102102          </button>
    103           <button type="button" data-cmd="link-prompt" title="Link (Ctrl+K)" aria-label="Link">
     103          <button type="button" data-cmd="link-prompt" title="<%= t('pedit.tb_link_title') %>" aria-label="<%= t('pedit.tb_link') %>">
    104104            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
    105105          </button>
    106           <button type="button" data-cmd="code-wrap" title="Code (inline)" aria-label="Code">
     106          <button type="button" data-cmd="code-wrap" title="<%= t('pedit.tb_code_title') %>" aria-label="<%= t('pedit.tb_code') %>">
    107107            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
    108108          </button>
    109109          <span class="pe-toolbar-sep" aria-hidden="true"></span>
    110           <button type="button" id="insert-image-btn" title="Afbeelding invoegen" aria-label="Afbeelding">
     110          <button type="button" id="insert-image-btn" title="<%= t('pedit.tb_image_title') %>" aria-label="<%= t('pedit.tb_image') %>">
    111111            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
    112112          </button>
    113           <button type="button" id="insert-track-btn" title="Track invoegen" aria-label="Track">
     113          <button type="button" id="insert-track-btn" title="<%= t('pedit.tb_track_title') %>" aria-label="<%= t('pedit.tb_track') %>">
    114114            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
    115115          </button>
    116           <button type="button" id="insert-playlist-btn" title="Playlist invoegen" aria-label="Playlist">
     116          <button type="button" id="insert-playlist-btn" title="<%= t('pedit.tb_playlist_title') %>" aria-label="<%= t('pedit.tb_playlist') %>">
    117117            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="15" y2="18"/><polygon points="3 5 3 13 9 9"/></svg>
    118118          </button>
    119           <button type="button" id="insert-embed-btn" title="Embed (YouTube, Spotify, SoundCloud, Vimeo…)" aria-label="Media embedden">
     119          <button type="button" id="insert-embed-btn" title="<%= t('pedit.tb_embed_title') %>" aria-label="<%= t('pedit.tb_embed') %>">
    120120            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><polygon points="10 9 15.5 12 10 15"/></svg>
    121121          </button>
    122122          <span class="pe-toolbar-spacer"></span>
    123           <button type="button" data-cmd="removeFormat" title="Wis opmaak" aria-label="Wis opmaak">
     123          <button type="button" data-cmd="removeFormat" title="<%= t('pedit.tb_clear') %>" aria-label="<%= t('pedit.tb_clear') %>">
    124124            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M5 5l14 14" stroke-linecap="round"/></svg>
    125125          </button>
    126           <button type="button" id="pe-fullscreen-btn" title="Volledig scherm" aria-label="Volledig scherm" aria-pressed="false">
     126          <button type="button" id="pe-fullscreen-btn" title="<%= t('pedit.tb_fullscreen') %>" aria-label="<%= t('pedit.tb_fullscreen') %>" aria-pressed="false">
    127127            <svg class="fs-icon-expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
    128128            <svg class="fs-icon-compress" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" y1="10" x2="21" y2="3"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
     
    135135             role="textbox"
    136136             aria-multiline="true"
    137              aria-label="Content"
    138              data-placeholder="Begin met schrijven…"></div>
     137             aria-label="<%= t('pedit.editor_aria') %>"
     138             data-placeholder="<%= t('pedit.editor_placeholder') %>"></div>
    139139
    140140        <%# Sticky "tik om te bewerken"-hint (alleen zichtbaar op touch + niet-fullscreen
    141141            via CSS). Puur visueel (pointer-events:none); de tik op het veld opent fullscreen. %>
    142         <div class="pe-edit-hint" aria-hidden="true">✎ Tik om te bewerken</div>
     142        <div class="pe-edit-hint" aria-hidden="true">✎ <%= t('pedit.tap_to_edit') %></div>
    143143
    144144        <%# Hidden field is what actually submits to the server. The visible
     
    153153        <div class="pe-editor-status">
    154154          <small id="content-upload-status" class="pe-status"></small>
    155           <span class="pe-char-count"><span id="char-count">0</span> tekens</span>
     155          <span class="pe-char-count"><span id="char-count">0</span> <%= t('pedit.chars') %></span>
    156156        </div>
    157157
     
    162162    <%# ── META ─────────────────────────────────────────────────── %>
    163163    <section class="pe-card">
    164       <div class="pe-section-title">Publicatie</div>
     164      <div class="pe-section-title"><%= t('pedit.s_publication') %></div>
    165165      <div class="pe-row pe-row-2">
    166166        <label class="pe-field">
    167           <span>Status</span>
     167          <span><%= t('pedit.f_status') %></span>
    168168          <% var _st = isNew ? 'published' : (post.status || 'draft'); %>
    169169          <select name="status">
    170             <option value="published" <%= _st === 'published' ? 'selected' : '' %>>Gepubliceerd</option>
    171             <option value="draft"     <%= _st === 'draft'     ? 'selected' : '' %>>Concept</option>
    172             <option value="archived"  <%= _st === 'archived'  ? 'selected' : '' %>>Gearchiveerd</option>
     170            <option value="published" <%= _st === 'published' ? 'selected' : '' %>><%= t('pedit.status_published') %></option>
     171            <option value="draft"     <%= _st === 'draft'     ? 'selected' : '' %>><%= t('pedit.status_draft') %></option>
     172            <option value="archived"  <%= _st === 'archived'  ? 'selected' : '' %>><%= t('pedit.status_archived') %></option>
    173173          </select>
    174174        </label>
    175175        <label class="pe-field">
    176           <span>Type</span>
     176          <span><%= t('pedit.f_type') %></span>
    177177          <select name="type">
    178178            <% var ptype = post.type || 'post'; %>
    179             <option value="post"  <%= ptype === 'post'  ? 'selected' : '' %>>Post</option>
    180             <option value="foto"  <%= ptype === 'foto'  ? 'selected' : '' %>>Foto</option>
    181             <option value="video" <%= ptype === 'video' ? 'selected' : '' %>>Video</option>
    182             <option value="audio" <%= ptype === 'audio' ? 'selected' : '' %>>Audio</option>
     179            <option value="post"  <%= ptype === 'post'  ? 'selected' : '' %>><%= t('pedit.type_post') %></option>
     180            <option value="foto"  <%= ptype === 'foto'  ? 'selected' : '' %>><%= t('pedit.type_foto') %></option>
     181            <option value="video" <%= ptype === 'video' ? 'selected' : '' %>><%= t('pedit.type_video') %></option>
     182            <option value="audio" <%= ptype === 'audio' ? 'selected' : '' %>><%= t('pedit.type_audio') %></option>
    183183          </select>
    184184        </label>
     
    188188          <label class="pe-checkbox">
    189189            <input type="checkbox" id="pin-toggle" <%= (Number(post.pinned) > 0) ? 'checked' : '' %>>
    190             <span>📌 Vastpinnen bovenaan</span>
     190            <span>📌 <%= t('pedit.pin_label') %></span>
    191191          </label>
    192192          <input type="hidden" name="pinned" id="pin-rank" value="<%= post.pinned || 0 %>">
    193193          <div class="pe-pin-pos" id="pin-pos" <%= (Number(post.pinned) > 0) ? '' : 'hidden' %>>
    194194            <span class="pe-pin-steps">
    195               <button type="button" class="pe-pin-btn" id="pin-up" aria-label="Hoger zetten">▲</button>
    196               <button type="button" class="pe-pin-btn" id="pin-down" aria-label="Lager zetten">▼</button>
     195              <button type="button" class="pe-pin-btn" id="pin-up" aria-label="<%= t('pedit.pin_up') %>">▲</button>
     196              <button type="button" class="pe-pin-btn" id="pin-down" aria-label="<%= t('pedit.pin_down') %>">▼</button>
    197197            </span>
    198198            <span class="pe-pin-label" id="pin-label"></span>
     
    201201        <label class="pe-checkbox">
    202202          <input type="checkbox" name="noindex" value="1" <%= post.noindex ? 'checked' : '' %>>
    203           <span>🚫 noindex (verberg voor zoekmachines)</span>
     203          <span>🚫 <%= t('pedit.noindex_label') %></span>
    204204        </label>
    205205        <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %>
    206206          <label class="pe-checkbox">
    207207            <input type="checkbox" name="fan_only" value="1" <%= post.fan_only ? 'checked' : '' %>>
    208             <span>🔒 Alleen voor ingelogde fans</span>
     208            <span>🔒 <%= t('pedit.fan_only_label') %></span>
    209209          </label>
    210210          <div style="margin-top:8px">
    211             <label style="display:block;font-size:12.5px;opacity:.8;margin-bottom:4px">📅 Publiceren op (laat leeg = direct)</label>
     211            <label style="display:block;font-size:12.5px;opacity:.8;margin-bottom:4px">📅 <%= t('pedit.publish_at_label') %></label>
    212212            <input type="datetime-local" name="publish_at" value="<%= post.publish_at ? String(post.publish_at).replace(' ','T').slice(0,16) : '' %>" style="padding:8px 10px;border-radius:8px;border:1px solid var(--rule,rgba(128,128,128,.4));background:transparent;color:inherit">
    213             <% if (post.status === 'scheduled' && post.publish_at) { %><div style="font-size:12px;opacity:.7;margin-top:4px">⏳ Ingepland voor <%= post.publish_at %></div><% } %>
     213            <% if (post.status === 'scheduled' && post.publish_at) { %><div style="font-size:12px;opacity:.7;margin-top:4px">⏳ <%= t('pedit.scheduled_for', { d: post.publish_at }) %></div><% } %>
    214214          </div>
    215215        <% } %>
     
    219219    <%# ── ACTIONS (sticky at bottom) ──────────────────────────── %>
    220220    <div class="pe-actions">
    221       <a href="<%= _base %><%= isNew ? '/' : '/' + post.slug %>" class="pe-btn">Annuleren</a>
     221      <a href="<%= _base %><%= isNew ? '/' : '/' + post.slug %>" class="pe-btn"><%= t('pedit.cancel') %></a>
    222222      <div class="pe-actions-spacer"></div>
    223223      <% if (!isNew && post.status !== 'published') { %>
    224         <button type="submit" name="action" value="publish" class="pe-btn pe-btn-success">📤 Publish</button>
     224        <button type="submit" name="action" value="publish" class="pe-btn pe-btn-success">📤 <%= t('pedit.publish') %></button>
    225225      <% } %>
    226       <button type="submit" class="pe-btn pe-btn-primary">💾 Opslaan</button>
     226      <button type="submit" class="pe-btn pe-btn-primary">💾 <%= t('pedit.save') %></button>
    227227    </div>
    228228  </form>
     
    983983      if (!coverField.files[0]) return;
    984984      coverStatus.classList.remove('is-error');
    985       coverStatus.textContent = 'Uploaden…';
     985      coverStatus.textContent = '<%= t('pedit.js_uploading') %>';
    986986      try {
    987987        const j = await uploadImage(coverField.files[0]);
    988988        coverUrl.value = j.url;
    989989        showCoverPreview(j.url);
    990         coverStatus.textContent = 'Geüpload ✓';
     990        coverStatus.textContent = '<%= t('pedit.js_uploaded') %> ✓';
    991991        setTimeout(() => { coverStatus.textContent = ''; }, 2000);
    992992      } catch (e) {
    993993        coverStatus.classList.add('is-error');
    994         coverStatus.textContent = 'Mislukt: ' + e.message;
     994        coverStatus.textContent = '<%= t('pedit.js_failed') %>: ' + e.message;
    995995      }
    996996    });
     
    10531053      // UUIDs are noisy — show a 6-char prefix for visual hint
    10541054      const v = String(value || '');
    1055       return 'Track ' + (v.length > 8 ? v.slice(0, 6) + '…' : v);
    1056     }
    1057     if (kind === 'album')    return 'Album: ' + value;
    1058     if (kind === 'playlist') return 'Playlist: ' + value;
     1055      return '<%= t('pedit.chip_track') %> ' + (v.length > 8 ? v.slice(0, 6) + '…' : v);
     1056    }
     1057    if (kind === 'album')    return '<%= t('pedit.chip_album') %> ' + value;
     1058    if (kind === 'playlist') return '<%= t('pedit.chip_playlist') %> ' + value;
    10591059    if (kind === 'embed') {
    10601060      const clean = String(value || '').replace(/^https?:\/\/(www\.)?/, '');
     
    11871187  }
    11881188  function linkPrompt() {
    1189     const url = window.prompt('Link URL (https://… of /pad)');
     1189    const url = window.prompt('<%= t('pedit.js_link_prompt') %>');
    11901190    if (!url) return;
    11911191    execCmd('createLink', url);
     
    12881288    if (fsBtn) {
    12891289      fsBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
    1290       fsBtn.title = on ? 'Klaar' : 'Volledig scherm';
     1290      fsBtn.title = on ? '<%= t('pedit.tb_done') %>' : '<%= t('pedit.tb_fullscreen') %>';
    12911291    }
    12921292    if (window.visualViewport) {
     
    13421342    editor.setAttribute('contenteditable', 'false');
    13431343    editor.classList.add('pe-tap-to-edit');
    1344     editor.setAttribute('data-placeholder', 'Tik om te schrijven…');
     1344    editor.setAttribute('data-placeholder', '<%= t('pedit.tap_to_write') %>');
    13451345    editor.addEventListener('click', function () {
    13461346      if (!isFs()) openFs();
     
    13901390  async function uploadAndInsertImage(file) {
    13911391    contentStatus.classList.remove('is-error');
    1392     contentStatus.textContent = 'Uploaden…';
     1392    contentStatus.textContent = '<%= t('pedit.js_uploading') %>';
    13931393    try {
    13941394      const j = await uploadImage(file);
     
    13961396      editor.focus({ preventScroll: true });
    13971397      document.execCommand('insertHTML', false, img);
    1398       contentStatus.textContent = 'Ingevoegd ✓';
     1398      contentStatus.textContent = '<%= t('pedit.js_inserted') %> ✓';
    13991399      setTimeout(() => { contentStatus.textContent = ''; }, 2000);
    14001400      updateCharCount();
    14011401    } catch (e) {
    14021402      contentStatus.classList.add('is-error');
    1403       contentStatus.textContent = 'Mislukt: ' + e.message;
     1403      contentStatus.textContent = '<%= t('pedit.js_failed') %>: ' + e.message;
    14041404    }
    14051405  }
     
    14591459  if (embedBtn) {
    14601460    embedBtn.addEventListener('click', () => {
    1461       const raw = window.prompt('Plak een media-URL om in te sluiten\n(YouTube, Spotify, SoundCloud, Vimeo, Apple Music, Bandcamp):');
     1461      const raw = window.prompt('<%= t('pedit.js_embed_prompt') %>');
    14621462      if (!raw) return;
    14631463      const url = raw.trim();
    1464       if (!/^https?:\/\//i.test(url)) { alert('Geef een volledige URL (https://…).'); return; }
     1464      if (!/^https?:\/\//i.test(url)) { alert('<%= t('pedit.js_embed_invalid') %>'); return; }
    14651465      insertChip('embed', url);
    14661466    });
     
    15011501      if (!filtered.length) {
    15021502        tpList.innerHTML = '';
    1503         tpEmpty.textContent = q ? 'Geen tracks gevonden voor "' + q + '"' : 'Nog geen tracks. Upload via Beheer → Audio.';
     1503        tpEmpty.textContent = q ? '<%= t('pedit.js_no_tracks_found') %> ' + q : '<%= t('pedit.js_no_tracks_yet') %>';
    15041504        tpList.appendChild(tpEmpty);
    15051505        return;
     
    15271527    async function loadTracks() {
    15281528      if (Array.isArray(tpCache)) return tpCache;
    1529       tpEmpty.textContent = 'Tracks laden…';
     1529      tpEmpty.textContent = '<%= t('pedit.js_tracks_loading') %>';
    15301530      try {
    15311531        const r = await fetch('/admin/playlists/api/tracks', { credentials: 'same-origin' });
     
    15341534      } catch (e) {
    15351535        tpCache = [];
    1536         tpEmpty.textContent = 'Kon tracks niet laden: ' + e.message;
     1536        tpEmpty.textContent = '<%= t('pedit.js_tracks_load_fail') %>: ' + e.message;
    15371537      }
    15381538      return tpCache;
     
    15961596    playlistBtn.addEventListener('click', async () => {
    15971597      if (typeof window.openPlaylistEditor !== 'function') {
    1598         alert('Playlist editor niet geladen');
     1598        alert('<%= t('pedit.js_playlist_editor_missing') %>');
    15991599        return;
    16001600      }
     
    16041604        if (j.ok && Array.isArray(j.playlists) && j.playlists.length > 0) {
    16051605          const choice = prompt(
    1606             'Bestaande playlists:\n\n' +
     1606            '<%= t('pedit.js_playlist_existing') %>\n\n' +
    16071607            j.playlists.map((p, i) => `${i + 1}. ${p.title} (${p.track_count} tracks)`).join('\n') +
    1608             '\n\nKies een nummer om in te voegen, leeg = nieuwe maken:'
     1608            '\n\n<%= t('pedit.js_playlist_choose') %>'
    16091609          );
    16101610          if (choice && /^\d+$/.test(choice.trim())) {
     
    16441644  <div class="tp-sheet" role="dialog" aria-modal="true" aria-labelledby="tp-title">
    16451645    <header class="tp-header">
    1646       <h2 id="tp-title" class="tp-h2">Track invoegen</h2>
    1647       <button type="button" class="tp-close" data-tp-close aria-label="Sluiten">
     1646      <h2 id="tp-title" class="tp-h2"><%= t('pedit.tp_title') %></h2>
     1647      <button type="button" class="tp-close" data-tp-close aria-label="<%= t('pedit.tp_close') %>">
    16481648        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
    16491649      </button>
     
    16531653        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
    16541654      </span>
    1655       <input type="search" class="tp-search" id="tp-search" placeholder="Zoek op titel of artiest…" autocomplete="off" inputmode="search">
     1655      <input type="search" class="tp-search" id="tp-search" placeholder="<%= t('pedit.tp_search_placeholder') %>" autocomplete="off" inputmode="search">
    16561656    </div>
    1657     <div class="tp-list" id="tp-list" role="listbox" aria-label="Tracks">
    1658       <div class="tp-empty" id="tp-empty">Tracks laden…</div>
     1657    <div class="tp-list" id="tp-list" role="listbox" aria-label="<%= t('pedit.tp_list_aria') %>">
     1658      <div class="tp-empty" id="tp-empty"><%= t('pedit.js_tracks_loading') %></div>
    16591659    </div>
    16601660  </div>
     
    16821682  function descr(n) {
    16831683    n = Number(n) || 0;
    1684     if (n <= 1) return 'bovenaan';
    1685     return n + 'e van boven';
     1684    if (n <= 1) return '<%= t('pedit.pin_top') %>';
     1685    return n + '<%= t('pedit.pin_nth_suffix') %>';
    16861686  }
    16871687  function render() {
  • src/views/pages/prutter-conversation.ejs

    rbbf9d1a r4885eab  
    11<div class="prutter-conv-page" data-conversation-id="<%= conversation.id %>" data-me="<%= user.id %>">
    22  <header class="prutter-conv-header">
    3     <a class="prutter-back" href="<%= siteUrlBase %>/prutter" aria-label="Terug naar inbox" title="Inbox">
     3    <a class="prutter-back" href="<%= siteUrlBase %>/prutter" aria-label="<%= t('prcv.back_aria') %>" title="<%= t('prcv.inbox') %>">
    44      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
    55    </a>
     
    1212    </div>
    1313    <div class="prutter-conv-id">
    14       <span class="prutter-conv-name"><%= other ? other.username : 'Onbekend' %></span>
     14      <span class="prutter-conv-name"><%= other ? other.username : t('prcv.unknown') %></span>
    1515      <% if (other) { %>
    16         <a class="prutter-conv-sub" href="/users/<%= encodeURIComponent(other.username) %>">Bekijk profiel</a>
     16        <a class="prutter-conv-sub" href="/users/<%= encodeURIComponent(other.username) %>"><%= t('prcv.view_profile') %></a>
    1717      <% } %>
    1818    </div>
     
    3434        hx-target="#prutter-thread"
    3535        hx-swap="beforeend">
    36     <textarea name="content" rows="1" maxlength="2000" placeholder="Bericht…" required></textarea>
    37     <button type="submit" class="btn btn-primary">Stuur</button>
     36    <textarea name="content" rows="1" maxlength="2000" placeholder="<%= t('prcv.placeholder') %>" required></textarea>
     37    <button type="submit" class="btn btn-primary"><%= t('prcv.send') %></button>
    3838  </form>
    3939</div>
  • src/views/pages/prutter-inbox.ejs

    rbbf9d1a r4885eab  
    22  <header class="prutter-header">
    33    <h1>Prutter</h1>
    4     <p class="prutter-tagline">Your direct messages on this site.</p>
     4    <p class="prutter-tagline"><%= t('prin.tagline') %></p>
    55  </header>
    66
    77  <% if (!conversations.length) { %>
    88    <p class="prutter-empty">
    9       No conversations yet. Open someone's profile and click <em>Send DM</em> to start one.
     9      <%= t('prin.empty') %>
    1010    </p>
    1111  <% } else { %>
     
    3131                <div class="prutter-conv-preview"><%= c.last_message_preview %></div>
    3232              <% } else { %>
    33                 <div class="prutter-conv-preview muted">Empty conversation</div>
     33                <div class="prutter-conv-preview muted"><%= t('prin.empty_conv') %></div>
    3434              <% } %>
    3535            </div>
  • src/views/pages/tag.ejs

    rbbf9d1a r4885eab  
    11<div class="container tag-page">
    22  <header class="tag-page-header">
    3     <p class="tag-eyebrow">Tag</p>
     3    <p class="tag-eyebrow"><%= t('ptag.eyebrow') %></p>
    44    <h1>#<%= tag %></h1>
    55    <p class="tag-meta">
    6       <%= posts.length %> post<%= posts.length === 1 ? '' : 's' %>
     6      <%= posts.length === 1 ? t('ptag.count_one', { n: posts.length }) : t('ptag.count_many', { n: posts.length }) %>
    77    </p>
    88  </header>
    99
    1010  <% if (!posts.length) { %>
    11     <p class="tag-empty">No posts with this tag yet.</p>
     11    <p class="tag-empty"><%= t('ptag.empty') %></p>
    1212  <% } else { %>
    1313    <ol class="tag-results">
     
    2626                 hx-get="/<%= p.slug %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
    2727                 hx-push-url="/<%= p.slug %>" hx-indicator="#pcms-loading">
    28                 <%= p.title || '(untitled)' %>
     28                <%= p.title || t('ptag.untitled') %>
    2929              </a>
    3030            </h2>
  • src/views/pages/type.ejs

    rbbf9d1a r4885eab  
    11<div class="container type-page">
    22  <header class="type-page-header">
    3     <p class="type-eyebrow">Type</p>
     3    <p class="type-eyebrow"><%= t('ptype.eyebrow') %></p>
    44    <h1><%= type.charAt(0).toUpperCase() + type.slice(1) %></h1>
    5     <p class="type-meta"><%= posts.length %> post<%= posts.length === 1 ? '' : 's' %></p>
     5    <p class="type-meta"><%= posts.length === 1 ? t('ptype.count_one', { n: posts.length }) : t('ptype.count_many', { n: posts.length }) %></p>
    66  </header>
    77
    88  <% if (!posts.length) { %>
    9     <p class="type-empty">No posts of this type yet.</p>
     9    <p class="type-empty"><%= t('ptype.empty') %></p>
    1010  <% } else { %>
    1111    <ol class="type-results">
     
    2424                 hx-get="/<%= p.slug %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
    2525                 hx-push-url="/<%= p.slug %>" hx-indicator="#pcms-loading">
    26                 <%= p.title || '(untitled)' %>
     26                <%= p.title || t('ptype.untitled') %>
    2727              </a>
    2828            </h2>
  • src/views/pages/user.ejs

    rbbf9d1a r4885eab  
    1919      <% } %>
    2020      <p class="user-stats">
    21         <%= posts.length %> post<%= posts.length === 1 ? '' : 's' %> on this site
     21        <%= posts.length %> <%= posts.length === 1 ? t('pusr.post_one') : t('pusr.post_many') %> <%= t('pusr.on_this_site') %>
    2222        <% if (totalPosts > posts.length) { %>
    23           &middot; <%= totalPosts %> total
     23          &middot; <%= t('pusr.total', { n: totalPosts }) %>
    2424        <% } %>
    2525        <% if (author.created_at) { %>
    26           &middot; joined <%= formatDate(author.created_at) %>
     26          &middot; <%= t('pusr.joined', { date: formatDate(author.created_at) }) %>
    2727        <% } %>
    2828      </p>
    2929      <% if (user && user.id !== author.id && site && site.enable_prutter && (typeof tenancy !== 'undefined' && tenancy === 'hub') && (typeof premiumUnlocked === 'undefined' || premiumUnlocked)) { %>
    3030        <p class="user-actions">
    31           <a href="<%= siteUrlBase %>/prutter/new?to=<%= encodeURIComponent(author.username) %>" class="btn btn-primary">💬 Send DM</a>
     31          <a href="<%= siteUrlBase %>/prutter/new?to=<%= encodeURIComponent(author.username) %>" class="btn btn-primary">💬 <%= t('pusr.send_dm') %></a>
    3232        </p>
    3333      <% } %>
     
    3636
    3737  <% if (!posts.length) { %>
    38     <p class="user-empty">No posts on this site yet.</p>
     38    <p class="user-empty"><%= t('pusr.empty') %></p>
    3939  <% } else { %>
    40     <h2 class="user-posts-heading">Posts</h2>
     40    <h2 class="user-posts-heading"><%= t('pusr.posts_heading') %></h2>
    4141    <ol class="user-posts">
    4242      <% posts.forEach(function(p) { %>
     
    5454                 hx-get="/<%= p.slug %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
    5555                 hx-push-url="/<%= p.slug %>" hx-indicator="#pcms-loading">
    56                 <%= p.title || '(untitled)' %>
     56                <%= p.title || t('pusr.untitled') %>
    5757              </a>
    5858            </h3>
  • src/views/pages/viewer-blocked.ejs

    rbbf9d1a r4885eab  
    22  <div class="vb-card">
    33    <div class="vb-ico" aria-hidden="true">👁️</div>
    4     <h1 class="vb-title">Kijker-modus</h1>
     4    <h1 class="vb-title"><%= t('vblk.title') %></h1>
    55    <p class="vb-text">
    6       Dit is een alleen-lezen account. Je kunt alles bekijken, maar
    7       <strong>niets wijzigen</strong> — opslaan, uploaden, verwijderen en
    8       reageren zijn uitgeschakeld.
     6      <%= t('vblk.text_before') %>
     7      <strong><%= t('vblk.text_strong') %></strong> <%= t('vblk.text_after') %>
    98    </p>
    109    <div class="vb-actions">
    11       <button type="button" class="vb-btn vb-btn-primary" onclick="history.back()">← Terug</button>
    12       <a class="vb-btn" href="/">Naar de hoofdpagina</a>
     10      <button type="button" class="vb-btn vb-btn-primary" onclick="history.back()">← <%= t('vblk.back') %></button>
     11      <a class="vb-btn" href="/"><%= t('vblk.to_home') %></a>
    1312    </div>
    1413  </div>
Note: See TracChangeset for help on using the changeset viewer.