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:
2026-05-15 22:53:27 +02:00
parent ba6c197f1b
commit a3d46f9756
19 changed files with 1149 additions and 238 deletions
+12 -9
View File
@@ -62,7 +62,7 @@ final class DbnBvjAnalyzerAgent
): array {
$engine = in_array($engine, ['azure_mini', 'azure_full', 'gpu'], true)
? $engine : 'azure_mini';
$language = in_array($language, ['en', 'no'], true) ? $language : 'en';
$language = dbnToolsNormalizeUiLanguage($language);
$controls = $this->normalizeControls($controls);
if (empty($uploadedFiles)) {
@@ -440,7 +440,7 @@ final class DbnBvjAnalyzerAgent
private function classifyDocument(string $docText, string $language): array
{
$locale = $language === 'no' ? 'Norwegian' : 'English';
$locale = dbnToolsLanguageName($language);
$excerpt = mb_substr($docText, 0, 6000, 'UTF-8');
$prompt = <<<PROMPT
@@ -492,7 +492,7 @@ PROMPT;
private function extractParties(string $docText, string $language): array
{
$locale = $language === 'no' ? 'Norwegian' : 'English';
$locale = dbnToolsLanguageName($language);
$excerpt = mb_substr($docText, 0, 12000, 'UTF-8');
$prompt = <<<PROMPT
@@ -540,7 +540,7 @@ PROMPT;
private function extractTimeline(string $docText, string $language): array
{
$locale = $language === 'no' ? 'Norwegian' : 'English';
$locale = dbnToolsLanguageName($language);
$excerpt = mb_substr($docText, 0, 12000, 'UTF-8');
$prompt = <<<PROMPT
@@ -600,7 +600,7 @@ PROMPT;
int $count,
string $language
): array {
$locale = $language === 'no' ? 'Norwegian' : 'English';
$locale = dbnToolsLanguageName($language);
$docType = $docMeta['doc_type'] ?? 'BVJ document';
$roleStr = $advocateRole !== '' ? $advocateRole : 'the affected party';
@@ -698,7 +698,7 @@ PROMPT;
string $additionalNotes,
?callable $emit = null
): array {
$locale = $language === 'no' ? 'Norwegian' : 'English';
$locale = dbnToolsLanguageName($language);
$roleStr = $advocateRole !== '' ? $advocateRole : 'the affected party';
$docType = $docMeta['doc_type'] ?? 'BVJ Document';
$docDate = $docMeta['doc_date'] ?? 'unknown date';
@@ -708,9 +708,12 @@ PROMPT;
$sourceCount = count($numberedSources);
if (empty($numberedSources)) {
$emptyBrief = $language === 'no'
? 'Ingen kildetreff ble funnet i korpuset for de valgte skivene og spørsmålene.'
: 'No corpus sources were retrieved for the selected slices and sub-questions.';
$emptyBrief = match (dbnToolsNormalizeUiLanguage($language)) {
'no' => 'Ingen kildetreff ble funnet i korpuset for de valgte skivene og spørsmålene.',
'uk' => 'Для вибраних розділів і підпитань не знайдено джерел у корпусі.',
'pl' => 'Nie znaleziono źródeł w korpusie dla wybranych sekcji i pytań pomocniczych.',
default => 'No corpus sources were retrieved for the selected slices and sub-questions.',
};
return [
'json' => [
'advocacy_brief' => $emptyBrief,