feat: Legal Tools v1 — multilingual landing, dashboard, SSO bridge
- Public landing page at / for unauthenticated users (EN/NO/UK/PL) - Authenticated / shows Case Workbench dashboard with manifesto strip, stats, and launched-tool grid (Transcribe, Timeline, BVJ, Advocate, Deep Research, Corpus) - Added includes/i18n.php with full 4-language translation layer - Extended layout.php to Case Workbench shell with tool rail, lang switcher - AI output language normalization extended to en/no/uk/pl in PHP agents - SSO token validation in bootstrap.php / index.php (dobetternorge.no bridge) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+36
-9
@@ -391,7 +391,14 @@ const VOCAB_PRESETS = {
|
||||
custom: '',
|
||||
};
|
||||
|
||||
let uiLang = localStorage.getItem('dbn-ui-lang') || 'en';
|
||||
let uiLang = window.DBN_TOOLS_LANG || localStorage.getItem('dbn-ui-lang') || 'en';
|
||||
|
||||
function syncOutputLanguage(lang) {
|
||||
const normalized = ['en', 'no', 'uk', 'pl'].includes(lang) ? lang : 'en';
|
||||
document.querySelectorAll('input[name="language"]').forEach((input) => {
|
||||
if (input.value === normalized) input.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
const TRANSCRIBE_I18N = {
|
||||
en: {
|
||||
@@ -670,6 +677,7 @@ function currentUiT(key, ...args) {
|
||||
function applyTranscribeI18n(lang) {
|
||||
uiLang = lang;
|
||||
localStorage.setItem('dbn-ui-lang', lang);
|
||||
syncOutputLanguage(lang);
|
||||
document.querySelectorAll('[data-i18n]').forEach((el) => {
|
||||
const text = currentUiT(el.dataset.i18n);
|
||||
if (text != null) el.textContent = text;
|
||||
@@ -695,6 +703,7 @@ function currentRedactT(key) {
|
||||
function applyRedactI18n(lang) {
|
||||
uiLang = lang;
|
||||
localStorage.setItem('dbn-ui-lang', lang);
|
||||
syncOutputLanguage(lang);
|
||||
document.querySelectorAll('[data-i18n]').forEach((el) => {
|
||||
const text = currentRedactT(el.dataset.i18n);
|
||||
if (text != null) el.textContent = text;
|
||||
@@ -750,6 +759,7 @@ function currentTimelineT(key) {
|
||||
function applyTimelineI18n(lang) {
|
||||
uiLang = lang;
|
||||
localStorage.setItem('dbn-ui-lang', lang);
|
||||
syncOutputLanguage(lang);
|
||||
document.querySelectorAll('[data-i18n]').forEach((el) => {
|
||||
const text = currentTimelineT(el.dataset.i18n);
|
||||
if (text != null) el.textContent = text;
|
||||
@@ -952,7 +962,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
els.form?.addEventListener('submit', runTool);
|
||||
els.passcodeForm?.addEventListener('submit', submitPasscode);
|
||||
els.healthButton.addEventListener('click', checkHealth);
|
||||
els.healthButton?.addEventListener('click', checkHealth);
|
||||
setupUpload();
|
||||
setupAliases();
|
||||
setupAudio();
|
||||
@@ -968,7 +978,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('uiLangSwitcher')) {
|
||||
applyTranscribeI18n(uiLang);
|
||||
}
|
||||
els.results.addEventListener('click', (e) => {
|
||||
els.results?.addEventListener('click', (e) => {
|
||||
if (e.target.closest('#exportCsvBtn')) exportTimelineCSV(lastTimelineEvents);
|
||||
if (e.target.closest('#dlTxt')) downloadTranscriptTxt();
|
||||
if (e.target.closest('#dlSrt')) downloadTranscriptSrt();
|
||||
@@ -978,7 +988,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (e.target.closest('#rdlDocx')) downloadRedactedDocx();
|
||||
});
|
||||
const activeTool = document.body.dataset.activeTool || state.activeTool;
|
||||
setTool(activeTool);
|
||||
if (els.form && tools[activeTool]) {
|
||||
setTool(activeTool);
|
||||
}
|
||||
|
||||
if (state.authenticated) {
|
||||
checkHealth();
|
||||
@@ -990,18 +1002,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
function setTool(toolName) {
|
||||
state.activeTool = toolName;
|
||||
const tool = tools[toolName];
|
||||
if (!tool || !els.toolKind || !els.input) return;
|
||||
const serverRenderedShell = els.tabs.some((tab) => tab.tagName === 'A');
|
||||
els.tabs.forEach((button) => {
|
||||
const active = button.dataset.tool === toolName;
|
||||
button.classList.toggle('is-active', active);
|
||||
button.setAttribute('aria-pressed', String(active));
|
||||
});
|
||||
|
||||
els.toolKind.textContent = tool.kind;
|
||||
els.toolTitle.textContent = tool.title;
|
||||
els.toolBadge.textContent = tool.badge;
|
||||
els.inputLabel.textContent = tool.label;
|
||||
if (!serverRenderedShell) {
|
||||
els.toolKind.textContent = tool.kind;
|
||||
els.toolTitle.textContent = tool.title;
|
||||
els.toolBadge.textContent = tool.badge;
|
||||
els.inputLabel.textContent = tool.label;
|
||||
}
|
||||
els.input.value = '';
|
||||
els.input.placeholder = tool.placeholder;
|
||||
if (!serverRenderedShell) {
|
||||
els.input.placeholder = tool.placeholder;
|
||||
}
|
||||
els.languageControl.classList.toggle('is-hidden', !tool.usesLanguage);
|
||||
els.redactionControl.classList.toggle('is-hidden', toolName !== 'redact');
|
||||
els.uploadZone.classList.toggle('is-hidden', toolName !== 'redact' && toolName !== 'timeline');
|
||||
@@ -1031,6 +1049,12 @@ async function submitPasscode(event) {
|
||||
throw new Error(data.error?.message || 'Credentials were not accepted.');
|
||||
}
|
||||
state.authenticated = true;
|
||||
if (!els.app) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const dest = params.get('return') || '/';
|
||||
window.location.href = dest.startsWith('/') && !dest.startsWith('//') ? dest : '/';
|
||||
return;
|
||||
}
|
||||
els.gate.classList.add('is-hidden');
|
||||
els.app.classList.remove('is-hidden');
|
||||
els.loginPassword.value = '';
|
||||
@@ -1116,6 +1140,7 @@ function resetUpload() {
|
||||
}
|
||||
|
||||
function setupUpload() {
|
||||
if (!els.uploadZone || !els.uploadInput) return;
|
||||
els.uploadZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
els.uploadZone.classList.add('is-drag-over');
|
||||
@@ -1217,6 +1242,7 @@ async function handleFiles(fileList) {
|
||||
}
|
||||
|
||||
function setupAliases() {
|
||||
if (!els.addAliasRow || !els.aliasRows) return;
|
||||
els.addAliasRow.addEventListener('click', () => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'alias-row';
|
||||
@@ -1287,6 +1313,7 @@ async function postJson(url, payload) {
|
||||
|
||||
function setBusy(isBusy) {
|
||||
const button = document.querySelector('#runButton');
|
||||
if (!button) return;
|
||||
button.disabled = isBusy;
|
||||
if (state.activeTool === 'transcribe') {
|
||||
button.textContent = isBusy ? currentUiT('running') : currentUiT('run');
|
||||
|
||||
Reference in New Issue
Block a user