Ignore:
Timestamp:
08/16/2026 01:53:01 PM (3 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
aebde2b
Parents:
d8f7d9c
Message:

Playlist-modal: de teksten door i18n

Ze stonden hard in mod/playlist-editor.js -- 34 stuks, van "Titel *" tot
"Upload mislukt". 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 daar valt niets van om.

EIGEN GEGEVENSBLOK, niet partials/page-data.ejs. pageData() pakt met
querySelector EEN blok, en deze modal hangt onder pagina's die er zelf al
een hebben (admin-playlists, post-edit). Een tweede blok zou daar
stilzwijgend genegeerd worden -- en dan was de modal alsnog Nederlands
zonder dat er een fout verscheen.

De terugval in T() is de SLEUTELNAAM en niet de Nederlandse tekst:
ontbreekt er iets, dan zie je DAT er iets ontbreekt in plaats van een
scherm dat er half vertaald uitziet en waarvan niemand merkt welke helft.

De test sluit de hele keten -- script vraagt T('x'), partial geeft 'x'
door, i18n kent 'ple.x' in alle drie de talen. Drie schakels die elk los
kunnen kloppen terwijl het geheel stuk is, en de vergeten TAAL is de
schakel die niemand ziet die in het Nederlands werkt. Hij vond meteen een
echte misser: saveBtn.textContent = 'Opslaan...' was ik overgeslagen.

Zijn eerste versie sloeg ook alarm op een COMMENTAAR dat die woorden
noemde. Dat is nu weggefilterd: een test die op uitleg valt leert je hem
negeren.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/mod/playlist-editor.js

    rd8f7d9c rd68ee3a  
    66
    77import { pageData } from './lib.js';
     8
     9/**
     10 * De teksten van deze modal, uit het gegevensblok van partials/playlist-editor.ejs.
     11 *
     12 * EIGEN BLOK en niet pageData(): die pakt met querySelector er EEN, en deze
     13 * modal hangt onder pagina's die er zelf al een hebben. Een tweede blok zou
     14 * daar genegeerd worden.
     15 *
     16 * De terugval is bewust de sleutelnaam en niet de Nederlandse tekst: ontbreekt
     17 * er iets, dan zie je DAT er iets ontbreekt in plaats van een pagina die er
     18 * half vertaald uitziet en waarvan niemand merkt welke helft.
     19 */
     20let _T = null;
     21function T(sleutel) {
     22  if (_T === null) {
     23    try {
     24      const el = document.querySelector('script[type="application/json"][data-playlist-editor-i18n]');
     25      _T = el ? (JSON.parse(el.textContent) || {}) : {};
     26    } catch (e) { _T = {}; }
     27  }
     28  return _T[sleutel] != null ? _T[sleutel] : sleutel;
     29}
    830
    931(function() {
     
    5476      if (Array.isArray(j.tracks)) tracks = j.tracks;
    5577    } catch (e) {
    56       alert('Tracks ophalen mislukt');
     78      alert(T('e_tracks'));
    5779      return;
    5880    }
    5981    if (!tracks.length) {
    60       alert('Geen audio-tracks beschikbaar. Upload eerst tracks via Admin → Audio.');
     82      alert(T('e_geen_tracks'));
    6183      return;
    6284    }
     
    7597    backdrop.className = 'pl-modal-backdrop';
    7698    backdrop.innerHTML = `
    77       <div class="pl-modal" role="dialog" aria-label="Playlist editor">
     99      <div class="pl-modal" role="dialog" aria-label="${T('dialoog')}">
    78100        <div class="pl-modal-header">
    79           <h3>${isEdit ? '✎ Playlist bewerken' : '+ Nieuwe playlist'}</h3>
    80           <button type="button" class="pl-modal-close" aria-label="Sluiten">×</button>
     101          <h3>${isEdit ? '✎ ' + T('t_edit') : '+ ' + T('t_new')}</h3>
     102          <button type="button" class="pl-modal-close" aria-label="${T('sluiten')}">×</button>
    81103        </div>
    82104        <div class="pl-modal-body">
     
    85107              <div class="pl-meta-grid">
    86108                <label class="pl-field pl-field-full">
    87                   <span>Titel *</span>
     109                  <span>${T('titel')}</span>
    88110                  <input type="text" id="pli-title" maxlength="200" autofocus value="${esc(initial.title)}">
    89111                </label>
    90112                <label class="pl-field">
    91                   <span>Artiest</span>
     113                  <span>${T('artiest')}</span>
    92114                  <input type="text" id="pli-artist" maxlength="200" value="${esc(initial.artist)}">
    93115                </label>
    94116                <label class="pl-field">
    95                   <span>Jaar</span>
     117                  <span>${T('jaar')}</span>
    96118                  <input type="number" id="pli-year" min="1900" max="2099" value="${initial.year || ''}">
    97119                </label>
    98120                <label class="pl-field">
    99                   <span>Type</span>
     121                  <span>${T('type')}</span>
    100122                  <select id="pli-kind">
    101                     <option value="album"    ${initial.kind === 'album' ? 'selected' : ''}>💿 Album (genummerd)</option>
    102                     <option value="playlist" ${initial.kind === 'playlist' ? 'selected' : ''}>📃 Playlist (track-covers)</option>
     123                    <option value="album"    ${initial.kind === 'album' ? 'selected' : ''}>💿 ${T('k_album')}</option>
     124                    <option value="playlist" ${initial.kind === 'playlist' ? 'selected' : ''}>📃 ${T('k_playlist')}</option>
    103125                  </select>
    104126                </label>
    105127                <label class="pl-field pl-uitgave">
    106                   <span>Uitgavedatum</span>
     128                  <span>${T('uitgave')}</span>
    107129                  <input type="date" id="pli-release-date" value="${esc(initial.release_date || '')}">
    108130                </label>
    109131                <label class="pl-field pl-uitgave">
    110                   <span>MusicBrainz release-id</span>
     132                  <span>${T('mb_release')}</span>
    111133                  <input type="text" id="pli-mb-release" maxlength="36" spellcheck="false"
    112134                         placeholder="00000000-0000-0000-0000-000000000000"
     
    114136                </label>
    115137                <div class="pl-field pl-field-full">
    116                   <span>Cover</span>
     138                  <span>${T('cover')}</span>
    117139                  <div class="pl-cover-row">
    118140                    <span class="pl-cover-thumb" id="pli-cover-thumb">
     
    121143                        : `<span class="pl-cover-empty">🎨</span>`}
    122144                    </span>
    123                     <input type="text" id="pli-cover" placeholder="https://… of upload" value="${esc(initial.cover)}" style="flex:1">
     145                    <input type="text" id="pli-cover" placeholder="${T('cover_url')}" value="${esc(initial.cover)}" style="flex:1">
    124146                  </div>
    125147                  <div class="pl-cover-upload-row">
    126148                    <input type="file" id="pli-cover-file" accept="image/jpeg,image/png,image/webp,image/gif" hidden>
    127                     <button type="button" class="pl-btn-small" id="pli-cover-pick">📷 Foto kiezen…</button>
     149                    <button type="button" class="pl-btn-small" id="pli-cover-pick">📷 ${T('cover_kies')}</button>
    128150                    <span class="pl-cover-status" id="pli-cover-status"></span>
    129151                  </div>
     
    131153              </div>
    132154              <div class="pl-section-title">
    133                 Tracks in playlist <span class="pl-track-count" id="pli-count">0</span>
    134                 <small>(sleep ⠿ om te ordenen)</small>
     155                ${T('tracks_in')} <span class="pl-track-count" id="pli-count">0</span>
     156                <small>${T('sleep_hint')}</small>
    135157              </div>
    136158              <div id="pli-selected" class="pl-selected-list"></div>
    137159            </div>
    138160            <div class="pl-editor-right">
    139               <div class="pl-section-title">Beschikbare tracks</div>
    140               <input type="search" id="pli-search" placeholder="Zoek..." class="pl-search-input">
     161              <div class="pl-section-title">${T('beschikbaar')}</div>
     162              <input type="search" id="pli-search" placeholder="${T('zoek')}" class="pl-search-input">
    141163              <div id="pli-available" class="pl-available-list"></div>
    142164            </div>
     
    144166        </div>
    145167        <div class="pl-modal-footer">
    146           <button type="button" class="btn" id="pli-cancel">Annuleren</button>
     168          <button type="button" class="btn" id="pli-cancel">${T('annuleren')}</button>
    147169          <button type="button" class="btn btn-primary" id="pli-save" disabled>
    148             ${isEdit ? 'Opslaan' : 'Aanmaken'}
     170            ${isEdit ? T('opslaan') : T('aanmaken')}
    149171          </button>
    150172        </div>
     
    199221    // requireAuth) dat een /media/-URL teruggeeft — dat heeft GEEN playlist-id
    200222    // nodig, dus uploaden kan ook al vóór het aanmaken. De URL belandt in het
    201     // cover-veld en wordt bij 'Aanmaken'/'Opslaan' met de playlist meegestuurd.
     223    // cover-veld en wordt bij het opslaan met de playlist meegestuurd.
    202224    const coverFileInput = $('#pli-cover-file');
    203225    const coverPickBtn   = $('#pli-cover-pick');
     
    210232        if (!file) return;
    211233        if (!/^image\//.test(file.type)) {
    212           coverStatus.textContent = 'Alleen afbeeldingen';
     234          coverStatus.textContent = T('e_alleen_afb');
    213235          coverStatus.className = 'pl-cover-status is-error';
    214236          return;
    215237        }
    216         coverStatus.textContent = 'Uploaden…';
     238        coverStatus.textContent = T('bezig');
    217239        coverStatus.className = 'pl-cover-status';
    218240        const fd = new FormData();
     
    223245          );
    224246          const j = await r.json();
    225           if (!r.ok || !j.url) throw new Error(j.error || 'Upload mislukt');
     247          if (!r.ok || !j.url) throw new Error(j.error || T('e_upload'));
    226248          const url = j.url || '';
    227249          coverEl.value = url;
     
    232254          coverStatus.className = 'pl-cover-status is-ok';
    233255        } catch (err) {
    234           coverStatus.textContent = 'Mislukt: ' + err.message;
     256          coverStatus.textContent = T('e_mislukt') + err.message;
    235257          coverStatus.className = 'pl-cover-status is-error';
    236258        }
     
    245267      countEl.textContent = selected.length;
    246268      if (selected.length === 0) {
    247         selectedEl.innerHTML = '<div class="pl-empty">Klik tracks rechts om toe te voegen.</div>';
     269        selectedEl.innerHTML = '<div class="pl-empty">' + T('leeg_sel') + '</div>';
    248270        updateSaveBtn();
    249271        return;
     
    256278          : `<span class="pl-row-cover pl-row-cover-empty">♪</span>`;
    257279        return `<div class="pl-row" data-id="${esc(id)}" data-pos="${i}">
    258           <span class="pl-row-handle" aria-label="Verslepen">⠿</span>
     280          <span class="pl-row-handle" aria-label="${T('versleep')}">⠿</span>
    259281          <span class="pl-row-num">${i + 1}</span>
    260282          ${cover}
     
    263285            ${t.artist ? `<span class="pl-row-artist">${esc(t.artist)}</span>` : ''}
    264286          </span>
    265           <button type="button" class="pl-row-x" data-id="${esc(id)}" aria-label="Verwijderen">×</button>
     287          <button type="button" class="pl-row-x" data-id="${esc(id)}" aria-label="${T('verwijder')}">×</button>
    266288        </div>`;
    267289      }).join('');
     
    286308      });
    287309      if (matches.length === 0) {
    288         availEl.innerHTML = '<div class="pl-empty">Geen resultaten.</div>';
     310        availEl.innerHTML = '<div class="pl-empty">' + T('geen_res') + '</div>';
    289311        return;
    290312      }
     
    297319          ? `<span class="pl-row-cover"><img src="${esc(t.cover)}" alt=""></span>`
    298320          : `<span class="pl-row-cover pl-row-cover-empty">♪</span>`;
    299         return `<div class="${cls.join(' ')}" data-id="${esc(t.id)}" ${t.playable ? '' : 'title="Track heeft geen audio-bestand"'}>
     321        return `<div class="${cls.join(' ')}" data-id="${esc(t.id)}" ${t.playable ? '' : `title="${T('geen_audio')}"`}>
    300322          ${cover}
    301323          <span class="pl-row-info">
     
    391413      const orig = saveBtn.textContent;
    392414      saveBtn.disabled = true;
    393       saveBtn.textContent = 'Opslaan...';
     415      saveBtn.textContent = T('bezig_opslaan');
    394416
    395417      const payload = {
     
    410432        const j = await api('POST', url, payload);
    411433        if (!j.ok) {
    412           alert('Opslaan mislukt: ' + (j.error || 'onbekend'));
     434          alert(T('e_opslaan') + (j.error || 'onbekend'));
    413435          saveBtn.disabled = false;
    414436          saveBtn.textContent = orig;
     
    421443        }
    422444      } catch (err) {
    423         alert('Opslaan mislukt: ' + err.message);
     445        alert(T('e_opslaan') + err.message);
    424446        saveBtn.disabled = false;
    425447        saveBtn.textContent = orig;
Note: See TracChangeset for help on using the changeset viewer.