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:
+50
-38
@@ -1,80 +1,91 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
$dashboardPage = 'documents';
|
||||
$dashboardTitle = 'Dokument';
|
||||
$dashboardTitle = dbnToolsT('dash_title_document', dbnToolsCurrentLanguage());
|
||||
$dashboardLead = '';
|
||||
require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
|
||||
$docId = (int)($_GET['id'] ?? 0);
|
||||
?>
|
||||
<div id="docViewRoot" data-doc-id="<?= $docId ?>">
|
||||
<p class="dash-loading">Laster dokument…</p>
|
||||
<p class="dash-loading" id="docLoadingMsg"></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
const I18N = window.DBN_I18N || {};
|
||||
const root = document.getElementById('docViewRoot');
|
||||
const docId = parseInt(root.dataset.docId, 10);
|
||||
const api = window.DBN_DASHBOARD.apiBase;
|
||||
const loc = I18N.locale || 'en-GB';
|
||||
|
||||
const loadMsg = document.getElementById('docLoadingMsg');
|
||||
if (loadMsg) loadMsg.textContent = I18N.loading || 'Loading…';
|
||||
|
||||
if (!docId) {
|
||||
root.innerHTML = '<div class="dash-error">Mangler dokument-id.</div>';
|
||||
root.innerHTML = '<div class="dash-error">' + (I18N.missing_doc_id || 'Missing document ID.') + '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
function safe(s) { return String(s ?? '').replace(/[&<>"]/g, c => ({ '&':'&','<':'<','>':'>','"':'"' }[c])); }
|
||||
function fmtDate(s) {
|
||||
if (!s) return '—';
|
||||
try { return new Date(s.replace(' ', 'T') + 'Z').toLocaleString('nb-NO', { dateStyle:'medium', timeStyle:'short' }); }
|
||||
try { return new Date(s.replace(' ', 'T') + 'Z').toLocaleString(loc, { dateStyle:'medium', timeStyle:'short' }); }
|
||||
catch (_) { return s; }
|
||||
}
|
||||
function fmtNum(n) { return n == null ? '—' : Number(n).toLocaleString('nb-NO'); }
|
||||
function fmtNum(n) { return n == null ? '—' : Number(n).toLocaleString(loc); }
|
||||
function statusPill(status) {
|
||||
const cls = { ready:'dash-status--ready', pending:'dash-status--pending', processing:'dash-status--processing', error:'dash-status--error' }[status] || 'dash-status--pending';
|
||||
const lbl = { ready:'Klar', pending:'Venter', processing:'Behandler', error:'Feil' }[status] || status;
|
||||
const lbl = {
|
||||
ready: I18N.status_ready || 'Ready', pending: I18N.status_pending || 'Pending',
|
||||
processing: I18N.status_processing || 'Processing', error: I18N.status_error || 'Error',
|
||||
}[status] || status;
|
||||
return '<span class="dash-status ' + cls + '">' + lbl + '</span>';
|
||||
}
|
||||
|
||||
fetch(api + '/documents.php?action=get&id=' + docId, { credentials: 'same-origin' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) throw new Error(data.error?.message || 'Dokument ikke funnet');
|
||||
if (!data.ok) throw new Error(data.error?.message || (I18N.doc_not_found || 'Document not found'));
|
||||
render(data.document, data.chunks || []);
|
||||
})
|
||||
.catch(err => {
|
||||
root.innerHTML = '<div class="dash-error">' + safe(err.message)
|
||||
+ '</div><p><a href="/dashboard/documents.php" class="dash-btn">← Tilbake</a></p>';
|
||||
+ '</div><p><a href="/dashboard/documents.php" class="dash-btn">← ' + (I18N.back || 'Back') + '</a></p>';
|
||||
});
|
||||
|
||||
function render(doc, chunks) {
|
||||
const back = I18N.back || '← Back';
|
||||
const delBtn = I18N.delete_btn || 'Delete';
|
||||
const html =
|
||||
'<section class="dash-card">'
|
||||
+ '<div class="dash-doc-head">'
|
||||
+ '<div>'
|
||||
+ '<h2>' + safe(doc.title) + '</h2>'
|
||||
+ '<p class="dash-doc-meta">' + statusPill(doc.status)
|
||||
+ ' · ' + fmtNum(doc.word_count) + ' ord'
|
||||
+ ' · ' + fmtNum(doc.chunk_count) + ' passasjer'
|
||||
+ ' · lagt til ' + fmtDate(doc.created_at) + '</p>'
|
||||
+ (doc.tags ? '<p class="dash-doc-meta">Tagger: ' + safe(doc.tags) + '</p>' : '')
|
||||
+ (doc.source_url ? '<p class="dash-doc-meta"><a href="' + safe(doc.source_url) + '" target="_blank" rel="noopener">Original kilde ↗</a></p>' : '')
|
||||
+ ' · ' + fmtNum(doc.word_count) + ' ' + (I18N.words || 'words')
|
||||
+ ' · ' + fmtNum(doc.chunk_count) + ' ' + (I18N.passages_label || 'passages')
|
||||
+ ' · ' + (I18N.added_at || 'added') + ' ' + fmtDate(doc.created_at) + '</p>'
|
||||
+ (doc.tags ? '<p class="dash-doc-meta">' + (I18N.tags_label || 'Tags:') + ' ' + safe(doc.tags) + '</p>' : '')
|
||||
+ (doc.source_url ? '<p class="dash-doc-meta"><a href="' + safe(doc.source_url) + '" target="_blank" rel="noopener">' + (I18N.source_url_label || 'Original source ↗') + '</a></p>' : '')
|
||||
+ '</div>'
|
||||
+ '<div>'
|
||||
+ '<a href="/dashboard/documents.php" class="dash-btn">← Tilbake</a> '
|
||||
+ '<button class="dash-btn dash-btn--danger" id="docDelete">Slett</button>'
|
||||
+ '<a href="/dashboard/documents.php" class="dash-btn">' + back + '</a> '
|
||||
+ '<button class="dash-btn dash-btn--danger" id="docDelete">' + delBtn + '</button>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
|
||||
+ '<nav class="dash-tabs" role="tablist">'
|
||||
+ '<button class="dash-tab is-active" data-tab="preview" role="tab">Forhåndsvisning</button>'
|
||||
+ '<button class="dash-tab" data-tab="chunks" role="tab">Passasjer (' + fmtNum(doc.chunk_count) + ')</button>'
|
||||
+ '<button class="dash-tab" data-tab="related" role="tab">Relatert</button>'
|
||||
+ '<button class="dash-tab" data-tab="edit" role="tab">Rediger</button>'
|
||||
+ '<button class="dash-tab is-active" data-tab="preview" role="tab">' + (I18N.tab_preview || 'Preview') + '</button>'
|
||||
+ '<button class="dash-tab" data-tab="chunks" role="tab">' + (I18N.tab_chunks || 'Passages') + ' (' + fmtNum(doc.chunk_count) + ')</button>'
|
||||
+ '<button class="dash-tab" data-tab="related" role="tab">' + (I18N.tab_related || 'Related') + '</button>'
|
||||
+ '<button class="dash-tab" data-tab="edit" role="tab">' + (I18N.tab_edit || 'Edit') + '</button>'
|
||||
+ '</nav>'
|
||||
|
||||
+ '<div class="dash-tab-panel is-active" data-panel="preview">'
|
||||
+ '<div class="dash-preview">' + safe(doc.content || '(tom)') + '</div>'
|
||||
+ '<div class="dash-preview">' + safe(doc.content || (I18N.content_empty || '(empty)')) + '</div>'
|
||||
+ '</div>'
|
||||
|
||||
+ '<div class="dash-tab-panel" data-panel="chunks">'
|
||||
@@ -83,22 +94,22 @@ $docId = (int)($_GET['id'] ?? 0);
|
||||
'<article class="dash-chunk">'
|
||||
+ (c.section_title ? '<p class="dash-chunk__section">' + safe(c.section_title) + '</p>' : '')
|
||||
+ safe(c.content) + '</article>').join('')
|
||||
: '<div class="dash-empty">Ingen passasjer indeksert ennå.</div>')
|
||||
: '<div class="dash-empty">' + (I18N.no_chunks || 'No passages indexed yet.') + '</div>')
|
||||
+ '</div>'
|
||||
|
||||
+ '<div class="dash-tab-panel" data-panel="related">'
|
||||
+ '<div class="dash-loading" id="relatedLoading">Laster relaterte autoriteter fra graphen…</div>'
|
||||
+ '<div class="dash-loading" id="relatedLoading">' + (I18N.loading_related || 'Loading related authorities from the graph…') + '</div>'
|
||||
+ '<div class="dash-related" id="relatedList" hidden></div>'
|
||||
+ '</div>'
|
||||
|
||||
+ '<div class="dash-tab-panel" data-panel="edit">'
|
||||
+ '<form id="docEditForm" style="display:grid; gap:0.85rem; max-width:560px;">'
|
||||
+ '<label>Tittel<input name="title" value="' + safe(doc.title) + '" style="width:100%;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<label>Kategori<input name="category" value="' + safe(doc.category || '') + '" style="width:100%;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<label>Tagger (komma-separert)<input name="tags" value="' + safe(doc.tags || '') + '" style="width:100%;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<label>Språk<input name="language" value="' + safe(doc.language || 'no') + '" maxlength="10" style="width:120px;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<label>Forfatter<input name="author" value="' + safe(doc.author || '') + '" style="width:100%;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<button type="submit" class="dash-btn dash-btn--primary" style="justify-self:start;">Lagre endringer</button>'
|
||||
+ '<label>' + (I18N.field_title || 'Title') + '<input name="title" value="' + safe(doc.title) + '" style="width:100%;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<label>' + (I18N.field_category || 'Category') + '<input name="category" value="' + safe(doc.category || '') + '" style="width:100%;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<label>' + (I18N.field_tags || 'Tags (comma-separated)') + '<input name="tags" value="' + safe(doc.tags || '') + '" style="width:100%;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<label>' + (I18N.field_lang || 'Language') + '<input name="language" value="' + safe(doc.language || 'no') + '" maxlength="10" style="width:120px;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<label>' + (I18N.field_author || 'Author') + '<input name="author" value="' + safe(doc.author || '') + '" style="width:100%;padding:0.5rem;border:1px solid var(--dbn-line);border-radius:8px;"></label>'
|
||||
+ '<button type="submit" class="dash-btn dash-btn--primary" style="justify-self:start;">' + (I18N.save_changes || 'Save changes') + '</button>'
|
||||
+ '<span id="docEditStatus" style="color:rgba(22,19,15,0.6);font-size:0.85rem;"></span>'
|
||||
+ '</form>'
|
||||
+ '</div>'
|
||||
@@ -137,19 +148,19 @@ $docId = (int)($_GET['id'] ?? 0);
|
||||
list.hidden = false;
|
||||
const items = data.results || [];
|
||||
if (!items.length) {
|
||||
list.innerHTML = '<div class="dash-empty">Dokumentet har ingen kjente siteringer i graf-databasen ennå.</div>';
|
||||
list.innerHTML = '<div class="dash-empty">' + (I18N.no_citations || 'No known citations in the graph database yet.') + '</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = items.map(it =>
|
||||
'<div class="dash-related__edge">'
|
||||
+ '<span class="dash-related__rel">' + safe(it.rel_type || '—') + '</span>'
|
||||
+ '<span class="dash-related__title">' + safe(it.title || it.ref || 'Ukjent') + '</span>'
|
||||
+ '<span class="dash-related__title">' + safe(it.title || it.ref || 'Unknown') + '</span>'
|
||||
+ '</div>').join('');
|
||||
})
|
||||
.catch(_ => {
|
||||
loading.hidden = true;
|
||||
list.hidden = false;
|
||||
list.innerHTML = '<div class="dash-empty">Graf-databasen er ikke tilgjengelig akkurat nå.</div>';
|
||||
list.innerHTML = '<div class="dash-empty">' + (I18N.graph_unavailable || 'Graph database is not available right now.') + '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -157,7 +168,7 @@ $docId = (int)($_GET['id'] ?? 0);
|
||||
const btn = document.getElementById('docDelete');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', () => {
|
||||
if (!confirm('Slette dette dokumentet permanent?')) return;
|
||||
if (!confirm(I18N.delete_doc_confirm || 'Delete this document permanently?')) return;
|
||||
btn.disabled = true;
|
||||
fetch(api + '/documents.php?action=delete', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
@@ -166,10 +177,10 @@ $docId = (int)($_GET['id'] ?? 0);
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) throw new Error(data.error?.message || 'Slett feilet');
|
||||
if (!data.ok) throw new Error(data.error?.message || 'Delete failed');
|
||||
location.href = '/dashboard/documents.php';
|
||||
})
|
||||
.catch(err => { alert('Feil: ' + err.message); btn.disabled = false; });
|
||||
.catch(err => { alert(err.message); btn.disabled = false; });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -184,7 +195,7 @@ $docId = (int)($_GET['id'] ?? 0);
|
||||
const el = form.elements[name];
|
||||
if (el) payload[name] = el.value;
|
||||
});
|
||||
status.textContent = 'Lagrer…';
|
||||
status.textContent = I18N.saving || 'Saving…';
|
||||
fetch(api + '/documents.php?action=update', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -192,10 +203,11 @@ $docId = (int)($_GET['id'] ?? 0);
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) throw new Error(data.error?.message || 'Lagring feilet');
|
||||
status.textContent = 'Lagret ' + new Date().toLocaleTimeString('nb-NO');
|
||||
if (!data.ok) throw new Error(data.error?.message || 'Save failed');
|
||||
const tmpl = I18N.saved_at || 'Saved {time}';
|
||||
status.textContent = tmpl.replace('{time}', new Date().toLocaleTimeString(loc));
|
||||
})
|
||||
.catch(err => status.textContent = 'Feil: ' + err.message);
|
||||
.catch(err => status.textContent = err.message);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user