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
-28
@@ -1,31 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
$dashboardPage = 'documents';
|
||||
$dashboardTitle = 'Dokumenter';
|
||||
$dashboardLead = 'Alle dokumenter i din private korpus. Klikk for å åpne, eller velg flere for bulk-handlinger.';
|
||||
$dashboardTitle = dbnToolsT('dash_title_docs', dbnToolsCurrentLanguage());
|
||||
$dashboardLead = dbnToolsT('dash_lead_docs', dbnToolsCurrentLanguage());
|
||||
require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
?>
|
||||
<section class="dash-card">
|
||||
<div class="dash-filters">
|
||||
<input type="search" id="docFilterQ" placeholder="Søk i titler eller tagger…" autocomplete="off">
|
||||
<input type="search" id="docFilterQ" placeholder="<?= htmlspecialchars(dbnToolsT('dash_filter_q_ph', $uiLang)) ?>" autocomplete="off">
|
||||
<select id="docFilterStatus">
|
||||
<option value="">Alle statuser</option>
|
||||
<option value="ready">Klar</option>
|
||||
<option value="pending">Venter</option>
|
||||
<option value="processing">Behandler</option>
|
||||
<option value="error">Feil</option>
|
||||
<option value=""><?= htmlspecialchars(dbnToolsT('dash_filter_all_status', $uiLang)) ?></option>
|
||||
<option value="ready" id="optReady"></option>
|
||||
<option value="pending" id="optPending"></option>
|
||||
<option value="processing" id="optProcessing"></option>
|
||||
<option value="error" id="optError"></option>
|
||||
</select>
|
||||
<button id="docBulkDelete" class="dash-btn dash-btn--danger" disabled>Slett valgte</button>
|
||||
<a href="/dashboard/upload.php" class="dash-btn dash-btn--primary" style="margin-left: auto;">+ Last opp</a>
|
||||
<button id="docBulkDelete" class="dash-btn dash-btn--danger" disabled><?= htmlspecialchars(dbnToolsT('dash_delete_selected', $uiLang)) ?></button>
|
||||
<a href="/dashboard/upload.php" class="dash-btn dash-btn--primary" style="margin-left: auto;"><?= htmlspecialchars(dbnToolsT('dash_upload_btn_short', $uiLang)) ?></a>
|
||||
</div>
|
||||
|
||||
<div id="docListWrap"><p class="dash-loading">Laster dokumenter…</p></div>
|
||||
<div id="docListWrap"><p class="dash-loading"></p></div>
|
||||
|
||||
<div class="dash-pager" id="docPager" hidden>
|
||||
<span id="docPagerLabel"></span>
|
||||
<div class="dash-pager__actions">
|
||||
<button class="dash-btn" id="docPagerPrev">← Forrige</button>
|
||||
<button class="dash-btn" id="docPagerNext">Neste →</button>
|
||||
<button class="dash-btn" id="docPagerPrev"><?= htmlspecialchars(dbnToolsT('dash_prev', $uiLang)) ?></button>
|
||||
<button class="dash-btn" id="docPagerNext"><?= htmlspecialchars(dbnToolsT('dash_next', $uiLang)) ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -33,9 +34,20 @@ 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 loc = I18N.locale || 'en-GB';
|
||||
const PAGE = 25;
|
||||
|
||||
const optReady = document.getElementById('optReady');
|
||||
const optPend = document.getElementById('optPending');
|
||||
const optProc = document.getElementById('optProcessing');
|
||||
const optErr = document.getElementById('optError');
|
||||
if (optReady) optReady.textContent = I18N.status_ready || 'Ready';
|
||||
if (optPend) optPend.textContent = I18N.status_pending || 'Pending';
|
||||
if (optProc) optProc.textContent = I18N.status_processing || 'Processing';
|
||||
if (optErr) optErr.textContent = I18N.status_error || 'Error';
|
||||
|
||||
const state = { offset: 0, total: 0, selected: new Set(), q: '', status: '' };
|
||||
|
||||
const $wrap = document.getElementById('docListWrap');
|
||||
@@ -50,13 +62,16 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
function safe(s) { return String(s ?? '').replace(/[&<>"]/g, c => ({ '&':'&','<':'<','>':'>','"':'"' }[c])); }
|
||||
function fmtDate(s) {
|
||||
if (!s) return '—';
|
||||
try { return new Date(s.replace(' ', 'T') + 'Z').toLocaleDateString('nb-NO', { day:'numeric', month:'short', year:'numeric' }); }
|
||||
try { return new Date(s.replace(' ', 'T') + 'Z').toLocaleDateString(loc, { day:'numeric', month:'short', year:'numeric' }); }
|
||||
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>';
|
||||
}
|
||||
|
||||
@@ -65,7 +80,7 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
if (state.q) qs.set('q', state.q);
|
||||
if (state.status) qs.set('status', state.status);
|
||||
|
||||
$wrap.innerHTML = '<p class="dash-loading">Laster dokumenter…</p>';
|
||||
$wrap.innerHTML = '<p class="dash-loading">' + (I18N.loading_docs || 'Loading documents…') + '</p>';
|
||||
fetch(api + '/documents.php?' + qs, { credentials:'same-origin' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
@@ -81,8 +96,9 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
function render(docs) {
|
||||
if (!docs.length) {
|
||||
$wrap.innerHTML = '<div class="dash-empty"><span class="dash-empty__icon">📭</span>'
|
||||
+ (state.q || state.status ? 'Ingen treff for valgt filter.'
|
||||
: 'Ingen dokumenter ennå. <a href="/dashboard/upload.php">Last opp ditt første</a>.')
|
||||
+ (state.q || state.status
|
||||
? (I18N.empty_filter || 'No results for selected filter.')
|
||||
: (I18N.empty_docs || 'No documents yet.') + ' <a href="/dashboard/upload.php">' + (I18N.empty_docs_link || 'Upload your first') + '</a>.')
|
||||
+ '</div>';
|
||||
$pager.hidden = true;
|
||||
return;
|
||||
@@ -93,8 +109,11 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
table.innerHTML =
|
||||
'<thead><tr>'
|
||||
+ '<th style="width:36px;"><input type="checkbox" id="docSelectAll"></th>'
|
||||
+ '<th>Tittel</th><th>Kategori</th><th>Status</th>'
|
||||
+ '<th>Passasjer</th><th>Lagt til</th>'
|
||||
+ '<th>' + (I18N.th_title || 'Title') + '</th>'
|
||||
+ '<th>' + (I18N.th_category || 'Category') + '</th>'
|
||||
+ '<th>' + (I18N.th_status || 'Status') + '</th>'
|
||||
+ '<th>' + (I18N.th_chunks || 'Passages') + '</th>'
|
||||
+ '<th>' + (I18N.th_added || 'Added') + '</th>'
|
||||
+ '</tr></thead>';
|
||||
const tbody = document.createElement('tbody');
|
||||
|
||||
@@ -120,7 +139,6 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
$wrap.innerHTML = '';
|
||||
$wrap.appendChild(table);
|
||||
|
||||
// Wire selection
|
||||
const all = document.getElementById('docSelectAll');
|
||||
all.addEventListener('change', () => {
|
||||
tbody.querySelectorAll('.doc-check').forEach(c => {
|
||||
@@ -140,7 +158,8 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
|
||||
const from = state.offset + 1;
|
||||
const to = Math.min(state.offset + docs.length, state.total);
|
||||
$pl.textContent = 'Viser ' + from + '–' + to + ' av ' + state.total;
|
||||
const tmpl = I18N.pager_showing || 'Showing {from}–{to} of {total}';
|
||||
$pl.textContent = tmpl.replace('{from}', from).replace('{to}', to).replace('{total}', state.total);
|
||||
$prev.disabled = state.offset === 0;
|
||||
$next.disabled = state.offset + PAGE >= state.total;
|
||||
$pager.hidden = false;
|
||||
@@ -148,7 +167,9 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
|
||||
function updateBulk() {
|
||||
$bulk.disabled = state.selected.size === 0;
|
||||
$bulk.textContent = state.selected.size > 0 ? 'Slett valgte (' + state.selected.size + ')' : 'Slett valgte';
|
||||
$bulk.textContent = state.selected.size > 0
|
||||
? (I18N.delete_n_selected || 'Delete selected ({n})').replace('{n}', state.selected.size)
|
||||
: (I18N.delete_selected || 'Delete selected');
|
||||
}
|
||||
|
||||
$prev.addEventListener('click', () => { state.offset = Math.max(0, state.offset - PAGE); load(); });
|
||||
@@ -163,7 +184,8 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
|
||||
$bulk.addEventListener('click', () => {
|
||||
if (!state.selected.size) return;
|
||||
if (!confirm('Slette ' + state.selected.size + ' dokumenter? Dette kan ikke angres.')) return;
|
||||
const msg = (I18N.delete_docs_confirm || 'Delete {n} documents? This cannot be undone.').replace('{n}', state.selected.size);
|
||||
if (!confirm(msg)) return;
|
||||
const ids = Array.from(state.selected);
|
||||
$bulk.disabled = true;
|
||||
fetch(api + '/documents.php?action=delete', {
|
||||
@@ -174,12 +196,12 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
})
|
||||
.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');
|
||||
state.selected.clear();
|
||||
updateBulk();
|
||||
load();
|
||||
})
|
||||
.catch(err => alert('Feil: ' + err.message));
|
||||
.catch(err => alert(err.message));
|
||||
});
|
||||
|
||||
load();
|
||||
|
||||
Reference in New Issue
Block a user