Index: test/playlist-editor-i18n.test.js
===================================================================
--- test/playlist-editor-i18n.test.js	(revision d68ee3aebf96df5e90d156f7a3d01115680ff3c7)
+++ test/playlist-editor-i18n.test.js	(revision d68ee3aebf96df5e90d156f7a3d01115680ff3c7)
@@ -0,0 +1,71 @@
+// De playlist-modal spreekt de taal van de pagina (16-8).
+//
+// Tot vandaag stonden de teksten hard in mod/playlist-editor.js. Dat viel niet
+// op omdat het toevallig de brontaal was: een Engelse of Duitse beheerder kreeg
+// een Nederlandse modal midden in een verder vertaalde pagina, en er is geen
+// foutmelding die dat meldt.
+//
+// Deze test sluit de KETEN, en dat is het punt -- er zijn drie schakels en elke
+// schakel kan los kloppen terwijl het geheel stuk is:
+//
+//   het script vraagt T('x')  ->  de partial zet 'x' in het gegevensblok
+//                             ->  i18n.js kent 'ple.x' in nl, en EN de
+//
+// Een nieuwe tekst toevoegen en een van de drie vergeten is de fout die dit
+// vangt, en juist de vergeten TAAL merkt niemand die in het Nederlands werkt.
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+
+const script = fs.readFileSync('src/assets/js/mod/playlist-editor.js', 'utf8');
+const partial = fs.readFileSync('src/views/partials/playlist-editor.ejs', 'utf8');
+const i18n = fs.readFileSync('src/services/i18n.js', 'utf8');
+
+// Alle sleutels die het script opvraagt. De declaratie van T() zelf niet.
+const gevraagd = new Set(
+  [...script.matchAll(/\bT\('([a-z0-9_]+)'\)/g)].map((m) => m[1])
+);
+
+test('het script vraagt uberhaupt sleutels op', () => {
+  assert.ok(gevraagd.size > 20, `maar ${gevraagd.size} sleutels gevonden -- klopt het patroon nog?`);
+});
+
+test('elke gevraagde sleutel staat in het gegevensblok van de partial', () => {
+  const blok = /data-playlist-editor-i18n><%-([\s\S]*?)%><\/script>/.exec(partial);
+  assert.ok(blok, 'geen i18n-blok in de partial');
+  const ontbreekt = [...gevraagd].filter((k) => !new RegExp(`\\b${k}:\\s*t\\('ple\\.${k}'\\)`).test(blok[1]));
+  assert.deepEqual(ontbreekt, [], `niet doorgegeven vanuit de partial: ${ontbreekt.join(', ')}`);
+});
+
+test('elke sleutel bestaat in alle drie de talen', () => {
+  // De taalblokken staan achter elkaar in i18n.js; per sleutel tellen hoe vaak
+  // hij voorkomt is genoeg om een vergeten vertaling te vinden.
+  const tekort = [];
+  for (const k of gevraagd) {
+    const n = (i18n.match(new RegExp(`'ple\\.${k}':`, 'g')) || []).length;
+    if (n !== 3) tekort.push(`${k} (${n}x)`);
+  }
+  assert.deepEqual(tekort, [], `niet in alle drie de talen: ${tekort.join(', ')}`);
+});
+
+test('er staat geen losse Nederlandse knoptekst meer in het script', () => {
+  // Grof maar effectief: dit zijn de woorden die er tot vandaag in stonden.
+  // Komt er een terug, dan is iemand de T() vergeten.
+  const verdacht = ['Annuleren', 'Opslaan', 'Aanmaken', 'Sluiten', 'Verslepen', 'Verwijderen',
+                    'Beschikbare tracks', 'Geen resultaten', 'Uitgavedatum', 'Jaar', 'Artiest'];
+  // COMMENTAAR ERAF. De eerste versie sloeg alarm op een regel die alleen
+  // uitlegde wat er gebeurt -- en een test die op uitleg valt leert je hem
+  // negeren.
+  const code = script.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/^\s*\/\/.*$/gm, ' ');
+  const gevonden = verdacht.filter((w) => new RegExp(`['"\`>]${w}`).test(code));
+  assert.deepEqual(gevonden, [], `hardgecodeerde tekst terug in het script: ${gevonden.join(', ')}`);
+});
+
+test('het eigen gegevensblok botst niet met dat van de pagina', () => {
+  // pageData() pakt met querySelector EEN blok. Zou deze partial het gewone
+  // data-page-data gebruiken, dan won het blok van de gastpagina en bleef de
+  // modal onvertaald -- zonder dat er iets omvalt.
+  assert.ok(partial.includes('data-playlist-editor-i18n'), 'eigen kenmerk weg');
+  assert.ok(!/data-page-data/.test(partial), 'de partial gebruikt het gedeelde blok');
+  assert.ok(script.includes('data-playlist-editor-i18n'), 'het script leest het eigen blok niet');
+});
