Index: src/views/pages/admin-audio.ejs
===================================================================
--- src/views/pages/admin-audio.ejs	(revision d727e920fc53e75604231284032475736ffcdc0d)
+++ src/views/pages/admin-audio.ejs	(revision bbf9d1a5d21d0049f96961fccd5666511634974a)
@@ -4,6 +4,6 @@
   <header class="ax-header">
     <div>
-      <h1>Audio tracks</h1>
-      <p class="ax-tagline">Site-level MP3s. Gebruik <code>[[track:&lt;id&gt;]]</code> in een post om een play-knop in te voegen.</p>
+      <h1><%= t('aaud.title') %></h1>
+      <p class="ax-tagline"><%= t('aaud.tagline_pre') %> <code>[[track:&lt;id&gt;]]</code> <%= t('aaud.tagline_post') %></p>
     </div>
   </header>
@@ -14,5 +14,5 @@
   <%# ── UPLOAD ──────────────────────────────────────────────── %>
   <section class="ax-card">
-    <div class="ax-card-title">Upload</div>
+    <div class="ax-card-title"><%= t('aaud.upload') %></div>
 
     <%# Bulk drag-drop zone. Multiple files at once, transcoded sequentially.
@@ -25,17 +25,17 @@
       <div class="ax-row ax-row-2">
         <label class="ax-field">
-          <span>Artiest <small>(toegepast op alle bestanden)</small></span>
-          <input type="text" id="batch-artist" placeholder="Optioneel">
+          <span><%= t('aaud.artist') %> <small><%= t('aaud.applied_all') %></small></span>
+          <input type="text" id="batch-artist" placeholder="<%= t('aaud.optional') %>">
         </label>
         <label class="ax-field">
-          <span>Album <small>(toegepast op alle bestanden)</small></span>
-          <input type="text" id="batch-album" placeholder="Optioneel">
+          <span><%= t('aaud.album') %> <small><%= t('aaud.applied_all') %></small></span>
+          <input type="text" id="batch-album" placeholder="<%= t('aaud.optional') %>">
         </label>
       </div>
       <label class="ax-field ax-file">
-        <span>Cover <small>(optioneel, toegepast op alle bestanden — jpg/png/webp/gif, max 5 MB)</small></span>
+        <span><%= t('aaud.cover') %> <small><%= t('aaud.cover_hint') %></small></span>
         <span class="ax-file-control">
-          <span class="ax-btn ax-btn-secondary ax-file-btn">🖼 Kies cover</span>
-          <span class="ax-file-name" data-empty>Geen bestand gekozen</span>
+          <span class="ax-btn ax-btn-secondary ax-file-btn">🖼 <%= t('aaud.choose_cover') %></span>
+          <span class="ax-file-name" data-empty><%= t('aaud.no_file') %></span>
           <input type="file" id="batch-cover" accept="image/jpeg,image/png,image/webp,image/gif">
         </span>
@@ -45,6 +45,6 @@
       <label class="ax-dropzone" id="audio-dropzone" tabindex="0">
         <span class="ax-dropzone-icon">🎵</span>
-        <strong class="ax-dropzone-title">Sleep audio hierheen</strong>
-        <span class="ax-dropzone-sub">of klik om bestanden te kiezen<br><small>mp3, m4a, ogg, opus, flac · max <%= maxBytesMb %> MB · wav max <%= maxWavMb %> MB</small></span>
+        <strong class="ax-dropzone-title"><%= t('aaud.drag_here') %></strong>
+        <span class="ax-dropzone-sub"><%= t('aaud.or_click') %><br><small>mp3, m4a, ogg, opus, flac · max <%= maxBytesMb %> MB · wav max <%= maxWavMb %> MB</small></span>
         <input type="file" id="audio-files" accept="audio/*" multiple>
       </label>
@@ -54,6 +54,6 @@
 
       <div class="ax-form-actions" id="upload-actions" hidden>
-        <button type="button" class="ax-btn ax-btn-primary" id="start-upload-btn">⬆ Start upload</button>
-        <button type="button" class="ax-btn" id="clear-queue-btn">Wis lijst</button>
+        <button type="button" class="ax-btn ax-btn-primary" id="start-upload-btn">⬆ <%= t('aaud.start_upload') %></button>
+        <button type="button" class="ax-btn" id="clear-queue-btn"><%= t('aaud.clear_list') %></button>
       </div>
     </div>
@@ -63,8 +63,8 @@
   <section class="ax-card">
     <div class="ax-card-title" style="display:flex;align-items:center;gap:.5rem">
-      Tracks <span class="ax-count"><%= tracks.length %></span>
+      <%= t('aaud.tracks') %> <span class="ax-count"><%= tracks.length %></span>
       <button type="button" class="ax-btn ax-btn-secondary" id="add-link-track-btn"
-              style="margin-left:auto;font-size:.85rem" title="Een track zonder audiobestand — alleen titel + open-in links">
-        + Track zonder audio
+              style="margin-left:auto;font-size:.85rem" title="<%= t('aaud.add_link_track_title') %>">
+        + <%= t('aaud.add_link_track') %>
       </button>
     </div>
@@ -73,7 +73,18 @@
       <div class="ax-empty">
         <div class="ax-empty-icon">♫</div>
-        <p>Nog geen tracks. Upload er een hierboven.</p>
+        <p><%= t('aaud.no_tracks') %></p>
       </div>
     <% } else { %>
+      <%
+        // Pre-fetch loop labels — `t` is shadowed by the loop var below.
+        var _aaudUntitled = t('aaud.untitled');
+        var _aaudPlay     = t('aaud.play');
+        var _aaudEdit     = t('aaud.edit');
+        var _aaudDlOn     = t('aaud.dl_on');
+        var _aaudDlOff    = t('aaud.dl_off');
+        var _aaudDelete   = t('aaud.delete');
+        var _aaudCopy     = t('aaud.copy_click');
+        var _aaudDelConfirm = t('aaud.delete_confirm');
+      %>
       <ul class="ax-list">
         <% tracks.forEach(function(t) { %>
@@ -86,5 +97,5 @@
 
             <div class="ax-track-meta">
-              <div class="ax-track-title" data-cell="title"><%= t.title || '(zonder titel)' %></div>
+              <div class="ax-track-title" data-cell="title"><%= t.title || _aaudUntitled %></div>
               <div class="ax-track-sub">
                 <span data-cell="artist"><%= t.artist || '—' %></span>
@@ -92,5 +103,5 @@
                 <span class="ax-track-sep">·</span><span><%= t.size ? Math.round(t.size/1024) + ' KB' : '—' %></span>
               </div>
-              <code class="ax-embed" data-copy="[[track:<%= t.id %>]]" title="Klik om te kopiëren">[[track:<%= t.id %>]]</code>
+              <code class="ax-embed" data-copy="[[track:<%= t.id %>]]" title="<%= _aaudCopy %>">[[track:<%= t.id %>]]</code>
             </div>
 
@@ -100,16 +111,16 @@
                         data-stream-url="<%= t.stream_url %>"
                         data-track-id="<%= t.id %>"
-                        aria-label="Afspelen" title="Afspelen">
+                        aria-label="<%= _aaudPlay %>" title="<%= _aaudPlay %>">
                   <span class="ax-track-play-icon" aria-hidden="true">▶</span>
                 </button>
               <% } %>
-              <button type="button" class="ax-icon-btn" data-track-edit data-id="<%= t.id %>" aria-label="Bewerken" title="Bewerken">✎</button>
+              <button type="button" class="ax-icon-btn" data-track-edit data-id="<%= t.id %>" aria-label="<%= _aaudEdit %>" title="<%= _aaudEdit %>">✎</button>
               <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %>
                 <button type="button" class="ax-icon-btn" data-track-dl data-id="<%= t.id %>" data-on="<%= t.downloadable ? '1' : '0' %>"
-                        aria-label="Download-voor-email" title="<%= t.downloadable ? 'Download-voor-email staat AAN — klik om uit te zetten' : 'Download-voor-email staat uit — klik om aan te zetten' %>"
+                        aria-label="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>" title="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>"
                         style="<%= t.downloadable ? 'color:var(--accent,#6b8f71)' : 'opacity:.5' %>">⬇</button>
               <% } %>
-              <form action="/admin/audio/<%= t.id %>/delete" method="post" onsubmit="return confirm('Track verwijderen?')" class="ax-track-delete">
-                <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="Verwijderen" title="Verwijderen">🗑</button>
+              <form action="/admin/audio/<%= t.id %>/delete" method="post" onsubmit="return confirm('<%= _aaudDelConfirm %>')" class="ax-track-delete">
+                <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _aaudDelete %>" title="<%= _aaudDelete %>">🗑</button>
               </form>
             </div>
@@ -122,8 +133,8 @@
   <% if ((typeof premiumUnlocked === 'undefined' || premiumUnlocked) && typeof embedUrl !== 'undefined') { %>
     <section class="ax-section" style="margin-top:1.5rem">
-      <h2 style="margin:0 0 .4rem">🔊 Embedbare speler</h2>
-      <p style="opacity:.75;font-size:.9rem;margin:0 0 .6rem">Plak deze code op je eigen website/blog om je muziek met deze speler in te sluiten:</p>
+      <h2 style="margin:0 0 .4rem">🔊 <%= t('aaud.embed_player') %></h2>
+      <p style="opacity:.75;font-size:.9rem;margin:0 0 .6rem"><%= t('aaud.embed_hint') %></p>
       <textarea readonly rows="3" onclick="this.select()" style="width:100%;font-family:monospace;font-size:12px;padding:10px;border-radius:8px;border:1px solid var(--rule);background:var(--paper-2);color:inherit;">&lt;iframe src="<%= embedUrl %>" width="100%" height="420" frameborder="0" loading="lazy" style="border-radius:12px"&gt;&lt;/iframe&gt;</textarea>
-      <p style="margin:.6rem 0 0"><a class="btn" href="<%= embedUrl %>" target="_blank">▶ Speler-voorbeeld openen</a></p>
+      <p style="margin:.6rem 0 0"><a class="btn" href="<%= embedUrl %>" target="_blank">▶ <%= t('aaud.preview_player') %></a></p>
     </section>
   <% } %>
@@ -573,5 +584,5 @@
         nameEl.removeAttribute('data-empty');
       } else {
-        nameEl.textContent = 'Geen bestand gekozen';
+        nameEl.textContent = '<%= t('aaud.no_file') %>';
         nameEl.setAttribute('data-empty', '');
       }
@@ -592,5 +603,5 @@
       if (icon) icon.textContent = playing ? '⏸' : '▶';
       btn.classList.toggle('is-playing', playing);
-      btn.setAttribute('aria-label', playing ? 'Pauzeren' : 'Afspelen');
+      btn.setAttribute('aria-label', playing ? '<%= t('aaud.pause') %>' : '<%= t('aaud.play') %>');
     }
 
@@ -695,9 +706,9 @@
   function setItemStatus(item, status, label) {
     const labels = {
-      queued:      'Wachten',
-      uploading:   'Uploaden…',
-      transcoding: 'Converteren…',
-      done:        '✓ Klaar',
-      error:       '✗ Fout',
+      queued:      '<%= t('aaud.st_queued') %>',
+      uploading:   '<%= t('aaud.st_uploading') %>',
+      transcoding: '<%= t('aaud.st_transcoding') %>',
+      done:        '✓ <%= t('aaud.st_done') %>',
+      error:       '✗ <%= t('aaud.st_error') %>',
     };
     item.status = status;
@@ -726,5 +737,5 @@
       li.innerHTML =
         '<div class="ax-queue-name"></div>' +
-        '<span class="ax-queue-status ax-queue-status--queued">Wachten</span>';
+        '<span class="ax-queue-status ax-queue-status--queued"><%= t('aaud.st_queued') %></span>';
       // Use textContent to avoid HTML-injection if a filename contains markup.
       li.querySelector('.ax-queue-name').textContent = file.name;
@@ -813,5 +824,5 @@
       } catch (err) {
         console.error('upload failed for', item.file.name, err);
-        setItemStatus(item, 'error', '✗ ' + (err.message || 'Mislukt'));
+        setItemStatus(item, 'error', '✗ ' + (err.message || '<%= t('aaud.failed') %>'));
       }
     }
@@ -864,5 +875,5 @@
       let data;
       try { data = await res.json(); }
-      catch (_) { throw new Error('Onverwacht serverantwoord (' + res.status + ')'); }
+      catch (_) { throw new Error('<%= t('aaud.err_unexpected') %> (' + res.status + ')'); }
 
       if (!res.ok || !data.ok) {
@@ -870,5 +881,5 @@
       }
 
-      setItemStatus(item, 'done', '✓ ' + (data.title || 'Klaar'));
+      setItemStatus(item, 'done', '✓ ' + (data.title || '<%= t('aaud.st_done') %>'));
     } catch (err) {
       clearTimeout(transcodeHint);
@@ -885,5 +896,5 @@
         el.classList.add('is-copied');
         const original = el.textContent;
-        el.textContent = '✓ gekopieerd';
+        el.textContent = '✓ <%= t('aaud.copied') %>';
         setTimeout(() => {
           el.classList.remove('is-copied');
@@ -903,12 +914,12 @@
           method: 'POST', credentials: 'same-origin',
           headers: { 'Content-Type': 'application/json' },
-          body: JSON.stringify({ title: 'Nieuwe track' }),
+          body: JSON.stringify({ title: '<%= t('aaud.new_track') %>' }),
         });
         const j = await r.json();
-        if (!r.ok || !j.ok) throw new Error(j.error || 'Aanmaken mislukt');
+        if (!r.ok || !j.ok) throw new Error(j.error || '<%= t('aaud.create_failed') %>');
         if (typeof window.openTrackEditor !== 'function') { location.reload(); return; }
         window.openTrackEditor({ id: j.id, onSaved: () => location.reload() });
       } catch (err) {
-        alert('Track aanmaken mislukt: ' + err.message);
+        alert('<%= t('aaud.create_failed') %>: ' + err.message);
       } finally {
         addLinkBtn.disabled = false;
@@ -923,5 +934,5 @@
     btn.addEventListener('click', () => {
       if (typeof window.openTrackEditor !== 'function') {
-        alert('Track editor niet geladen');
+        alert('<%= t('aaud.editor_not_loaded') %>');
         return;
       }
@@ -936,5 +947,5 @@
           const artistEl = row.querySelector('[data-cell="artist"]');
           const albumEl  = row.querySelector('[data-cell="album"]');
-          if (titleEl)  titleEl.textContent  = track.title  || '(zonder titel)';
+          if (titleEl)  titleEl.textContent  = track.title  || '<%= t('aaud.untitled') %>';
           if (artistEl) artistEl.textContent = track.artist || '—';
           if (albumEl)  {
@@ -984,8 +995,8 @@
         btn.style.opacity = want ? '1' : '.5';
         btn.title = want
-          ? 'Download-voor-email staat AAN — klik om uit te zetten'
-          : 'Download-voor-email staat uit — klik om aan te zetten';
+          ? '<%= t('aaud.dl_on') %>'
+          : '<%= t('aaud.dl_off') %>';
       } catch (e) {
-        alert('Kon niet wijzigen: ' + (e.message || e));
+        alert('<%= t('aaud.change_failed') %>: ' + (e.message || e));
       } finally {
         btn.disabled = false;
