i18n all /dashboard/ corpus pages for en/no/uk/pl

- Add require_once bootstrap.php to all 6 dashboard page files so
  dbnToolsT() is available before layout_dashboard.php is included
- Add dash_upload_category_lbl key to no/uk/pl sections of i18n.php
  (was only in English); Kategori/Категорія/Kategoria
- Fix broken ternary on upload.php Category label — replace with
  dbnToolsT('dash_upload_category_lbl', $uiLang)
- layout_dashboard.php outputs window.DBN_I18N with all js_* keys
  so dashboard JS reads locale-aware strings from PHP translations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 20:10:57 +02:00
parent 90117fa9de
commit a9e64b65ce
8 changed files with 886 additions and 197 deletions
+54 -51
View File
@@ -1,16 +1,17 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../includes/bootstrap.php';
$dashboardPage = 'upload';
$dashboardTitle = 'Last opp dokumenter';
$dashboardLead = 'Last opp PDF, DOCX, eller TXT — eller lim inn tekst eller en URL. Innholdet chunkes, embedes og indekseres i din private korpus.';
$dashboardTitle = dbnToolsT('dash_title_upload', dbnToolsCurrentLanguage());
$dashboardLead = dbnToolsT('dash_lead_upload', dbnToolsCurrentLanguage());
require_once __DIR__ . '/../includes/layout_dashboard.php';
?>
<section class="dash-card">
<div class="dash-card__head">
<h2>Velg kilde</h2>
<h2><?= htmlspecialchars(dbnToolsT('dash_upload_source', $uiLang)) ?></h2>
<div class="dash-card__actions">
<button class="dash-btn upload-mode is-active" data-mode="file">Fil</button>
<button class="dash-btn upload-mode" data-mode="text">Lim inn tekst</button>
<button class="dash-btn upload-mode is-active" data-mode="file"><?= htmlspecialchars(dbnToolsT('dash_upload_file_btn', $uiLang)) ?></button>
<button class="dash-btn upload-mode" data-mode="text"><?= htmlspecialchars(dbnToolsT('dash_upload_text_btn', $uiLang)) ?></button>
<button class="dash-btn upload-mode" data-mode="url">URL</button>
</div>
</div>
@@ -19,39 +20,39 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
<label class="upload-drop" id="upDrop">
<input type="file" name="file" id="upFile" accept=".pdf,.docx,.txt" hidden>
<span class="upload-drop__icon">📥</span>
<strong>Slipp filen her, eller klikk for å bla</strong>
<small>PDF · DOCX · TXT · maks 8 MB · automatisk OCR for skannede PDF-er</small>
<strong id="upDropHint"><?= htmlspecialchars(dbnToolsT('dash_upload_drop_strong', $uiLang)) ?></strong>
<small><?= htmlspecialchars(dbnToolsT('dash_upload_drop_small', $uiLang)) ?></small>
</label>
<div class="upload-meta">
<label>Tittel<input name="title" placeholder="Auto fra filnavn om tom"></label>
<label>Kategori<input name="category" placeholder="f.eks. barnevern, familierett"></label>
<label>Tagger<input name="tags" placeholder="komma,separert,liste"></label>
<label>Språk<input name="language" value="no" maxlength="10"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_title_lbl', $uiLang)) ?><input name="title" placeholder="<?= htmlspecialchars(dbnToolsT('dash_upload_title_ph', $uiLang)) ?>"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_category_lbl', $uiLang)) ?><input name="category" placeholder="<?= htmlspecialchars(dbnToolsT('dash_upload_category_ph', $uiLang)) ?>"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_tags_lbl', $uiLang)) ?><input name="tags" placeholder="comma,separated,list"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_lang_lbl', $uiLang)) ?><input name="language" value="no" maxlength="10"></label>
</div>
<button type="submit" class="dash-btn dash-btn--primary" style="justify-self:start;">Last opp og indekser</button>
<button type="submit" class="dash-btn dash-btn--primary" style="justify-self:start;"><?= htmlspecialchars(dbnToolsT('dash_upload_btn_file', $uiLang)) ?></button>
</form>
<form id="upTextForm" class="upload-form" hidden style="display:grid; gap:0.85rem;">
<label>Tittel<input name="title" required placeholder="Gi notatet en tittel"></label>
<label>Innhold<textarea name="content" rows="12" required placeholder="Lim inn tekst her — minst 30 tegn"></textarea></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_title_lbl', $uiLang)) ?><input name="title" required placeholder="<?= htmlspecialchars(dbnToolsT('dash_upload_note_title_ph', $uiLang)) ?>"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_content_lbl', $uiLang)) ?><textarea name="content" rows="12" required placeholder="<?= htmlspecialchars(dbnToolsT('dash_upload_content_ph', $uiLang)) ?>"></textarea></label>
<div class="upload-meta">
<label>Kategori<input name="category" placeholder="f.eks. notat"></label>
<label>Tagger<input name="tags"></label>
<label>Språk<input name="language" value="no" maxlength="10"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_tags_lbl', $uiLang)) ?><input name="category" placeholder="e.g. note"></label>
<label>Tags<input name="tags"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_lang_lbl', $uiLang)) ?><input name="language" value="no" maxlength="10"></label>
</div>
<button type="submit" class="dash-btn dash-btn--primary" style="justify-self:start;">Lagre i korpus</button>
<button type="submit" class="dash-btn dash-btn--primary" style="justify-self:start;"><?= htmlspecialchars(dbnToolsT('dash_upload_btn_text', $uiLang)) ?></button>
</form>
<form id="upUrlForm" class="upload-form" hidden style="display:grid; gap:0.85rem;">
<label>URL<input name="url" type="url" required placeholder="https://lovdata.no/dokument/…"></label>
<label>Tittel<input name="title" placeholder="Tom = bruker URL"></label>
<label>URL<input name="url" type="url" required placeholder="<?= htmlspecialchars(dbnToolsT('dash_upload_url_ph', $uiLang)) ?>"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_title_lbl', $uiLang)) ?><input name="title" placeholder="<?= htmlspecialchars(dbnToolsT('dash_upload_url_title_ph', $uiLang)) ?>"></label>
<div class="upload-meta">
<label>Kategori<input name="category"></label>
<label>Tagger<input name="tags"></label>
<label>Språk<input name="language" value="no" maxlength="10"></label>
<label>Category<input name="category"></label>
<label>Tags<input name="tags"></label>
<label><?= htmlspecialchars(dbnToolsT('dash_upload_lang_lbl', $uiLang)) ?><input name="language" value="no" maxlength="10"></label>
</div>
<button type="submit" class="dash-btn dash-btn--primary" style="justify-self:start;">Hent og indekser</button>
<small style="color:rgba(22,19,15,0.55);">URLer kjøres i bakgrunnen — sjekk «Dokumenter» for status.</small>
<button type="submit" class="dash-btn dash-btn--primary" style="justify-self:start;"><?= htmlspecialchars(dbnToolsT('dash_upload_btn_url', $uiLang)) ?></button>
<small style="color:rgba(22,19,15,0.55);"><?= htmlspecialchars(dbnToolsT('dash_upload_url_note', $uiLang)) ?></small>
</form>
<div id="upStatus" class="upload-status" hidden></div>
@@ -85,7 +86,9 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
<script>
(function () {
'use strict';
const api = window.DBN_DASHBOARD.apiBase;
const I18N = window.DBN_I18N || {};
const api = window.DBN_DASHBOARD.apiBase;
const dropHintDefault = I18N.upload_drop_hint || 'Drop file here, or click to browse';
const forms = {
file: document.getElementById('upFileForm'),
@@ -104,9 +107,9 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
});
});
// Drag-drop wiring for file
const drop = document.getElementById('upDrop');
const fileInput = document.getElementById('upFile');
const dropHintEl = document.getElementById('upDropHint');
if (drop && fileInput) {
drop.addEventListener('click', () => fileInput.click());
['dragenter', 'dragover'].forEach(ev => drop.addEventListener(ev, e => {
@@ -118,12 +121,12 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
drop.addEventListener('drop', e => {
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
drop.querySelector('strong').textContent = e.dataTransfer.files[0].name;
if (dropHintEl) dropHintEl.textContent = e.dataTransfer.files[0].name;
}
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length) {
drop.querySelector('strong').textContent = fileInput.files[0].name;
if (fileInput.files.length && dropHintEl) {
dropHintEl.textContent = fileInput.files[0].name;
}
});
}
@@ -139,54 +142,54 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
forms.file.addEventListener('submit', (e) => {
e.preventDefault();
const fd = new FormData(forms.file);
if (!fileInput.files.length) { setStatus('Velg en fil først.', 'err'); return; }
setStatus('Laster opp og indekserer…');
if (!fileInput.files.length) { setStatus(I18N.upload_select_file || 'Select a file first.', 'err'); return; }
setStatus(I18N.upload_indexing || 'Uploading and indexing…');
fetch(api + '/upload.php', { method: 'POST', credentials: 'same-origin', body: fd })
.then(r => r.json()).then(handleResult).catch(err => setStatus('Feil: ' + safe(err.message), 'err'));
.then(r => r.json()).then(handleResult).catch(err => setStatus(' ' + safe(err.message), 'err'));
});
forms.text.addEventListener('submit', (e) => {
e.preventDefault();
const payload = { kind: 'text' };
new FormData(forms.text).forEach((v, k) => payload[k] = v);
setStatus('Indekserer…');
setStatus(I18N.upload_saving || 'Indexing…');
fetch(api + '/upload.php', {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}).then(r => r.json()).then(handleResult).catch(err => setStatus('Feil: ' + safe(err.message), 'err'));
}).then(r => r.json()).then(handleResult).catch(err => setStatus(' ' + safe(err.message), 'err'));
});
forms.url.addEventListener('submit', (e) => {
e.preventDefault();
const payload = { kind: 'url' };
new FormData(forms.url).forEach((v, k) => payload[k] = v);
setStatus('Køer URL for henting…');
setStatus(I18N.upload_queuing || 'Queuing URL for fetching…');
fetch(api + '/upload.php', {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}).then(r => r.json()).then(handleResult).catch(err => setStatus('Feil: ' + safe(err.message), 'err'));
}).then(r => r.json()).then(handleResult).catch(err => setStatus(' ' + safe(err.message), 'err'));
});
function handleResult(data) {
if (data.status === 'ready') {
setStatus('✅ Indeksert! '
+ (data.chunks || 0) + ' passasjer lagt til. '
+ '<a href="/dashboard/document.php?id=' + data.document_id + '">Åpne dokument</a> · '
+ '<a href="/dashboard/documents.php">Se alle</a>', 'ok');
const msg = (I18N.upload_indexed || '✅ Indexed! {n} passages added.').replace('{n}', data.chunks || 0);
setStatus(msg
+ ' <a href="/dashboard/document.php?id=' + data.document_id + '">' + (I18N.upload_open_doc || 'Open document') + '</a>'
+ ' · <a href="/dashboard/documents.php">' + (I18N.see_all || 'See all') + '</a>', 'ok');
['file', 'text', 'url'].forEach(m => forms[m].reset());
if (drop) drop.querySelector('strong').textContent = 'Slipp filen her, eller klikk for å bla';
if (dropHintEl) dropHintEl.textContent = dropHintDefault;
} else if (data.status === 'pending') {
setStatus('📥 Lagt i kø. '
+ '<a href="/dashboard/documents.php">Følg fremdriften i Dokumenter</a>.', 'ok');
setStatus((I18N.upload_queued || '📥 Queued.')
+ ' <a href="/dashboard/documents.php">' + (I18N.upload_follow_docs || 'Follow progress in Documents') + '</a>.', 'ok');
pollUntilDone(data.document_id);
} else if (data.status === 'error') {
setStatus('❌ ' + safe(data.error?.message || 'Indeksering feilet.'), 'err');
setStatus('❌ ' + safe(data.error?.message || 'Indexing failed.'), 'err');
} else if (!data.ok) {
setStatus('❌ ' + safe(data.error?.message || 'Opplasting feilet.'), 'err');
setStatus('❌ ' + safe(data.error?.message || 'Upload failed.'), 'err');
} else {
setStatus('Uventet svar.', 'err');
setStatus('Unexpected response.', 'err');
}
}
@@ -200,13 +203,13 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
const s = (data.statuses || []).find(x => x.id === docId);
if (!s) return;
if (s.status === 'ready') {
setStatus('✅ Bakgrunnsjobb ferdig. '
+ s.chunk_count + ' passasjer indeksert. '
+ '<a href="/dashboard/document.php?id=' + docId + '">Åpne dokument</a>', 'ok');
const msg = (I18N.upload_bg_done || '✅ Background job done. {n} passages indexed.').replace('{n}', s.chunk_count);
setStatus(msg
+ ' <a href="/dashboard/document.php?id=' + docId + '">' + (I18N.upload_open_doc || 'Open document') + '</a>', 'ok');
return;
}
if (s.status === 'error') {
setStatus('❌ ' + safe(s.error_message || 'Bakgrunnsjobb feilet.'), 'err');
setStatus('❌ ' + safe(s.error_message || 'Background job failed.'), 'err');
return;
}
setTimeout(tick, 3000);