feat: add Legal Translation tool (translate.php)

New dedicated tool for translating Norwegian legal documents (Barnevernet
letters, court decisions, correspondence) into the user's chosen language
with legal-terminology annotations.

- translate.php: new tool page with source/target language selectors,
  4-way UI lang switcher, file upload, doc picker, streaming results
- api/translate.php: NDJSON streaming endpoint; Azure GPT-4o-mini with
  legal-aware prompt that preserves Norwegian statute refs verbatim and
  annotates terms with no target-language equivalent; 2-credit cost
- assets/js/translate.js: form handler, NDJSON stream reader, copy button
- assets/css/tools.css: .lt-* styles for translation result + annotations
- includes/i18n.php: 22 lt_* keys × 4 languages; translate entry in nav
- includes/FreeTier.php: translate → 2 credits
- includes/CaseResults.php + case-result.php: translate in eligible tools,
  toolLabel, toolIcon, deriveTitle, rendering block, rerun map

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 09:59:06 +02:00
parent 21c092e0d0
commit effd3289b4
8 changed files with 842 additions and 1 deletions
+28
View File
@@ -298,6 +298,33 @@ function crField(string $label, string $value): string {
<?php endif; ?>
<?= crListBlock('What Remains Uncertain', (array)($output['what_remains_uncertain'] ?? [])) ?>
<?php elseif ($toolSlug === 'translate'): ?>
<?php if (!empty($output['translated_text'])): ?>
<div class="cr-block">
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.6rem;">
<h3 style="margin:0">Translation</h3>
<?php if (!empty($output['source_lang']) && !empty($output['target_lang'])): ?>
<span style="font-size:0.8rem;font-family:monospace;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:4px;padding:0.2rem 0.5rem;color:#475569;"><?= htmlspecialchars(strtoupper((string)$output['source_lang'])) ?> → <?= htmlspecialchars(strtoupper((string)$output['target_lang'])) ?></span>
<?php endif; ?>
</div>
<pre style="white-space:pre-wrap;font-size:0.93rem;line-height:1.75;border:1px solid #cbd5e1;border-radius:8px;padding:1.1rem 1.4rem;background:#f8fafc;color:#1e293b;max-height:60vh;overflow-y:auto;"><?= htmlspecialchars((string)$output['translated_text']) ?></pre>
</div>
<?php endif; ?>
<?php if (!empty($output['annotations']) && is_array($output['annotations'])): ?>
<div class="cr-block">
<h3>Legal term notes</h3>
<?php foreach ($output['annotations'] as $ann): if (!is_array($ann)) continue; ?>
<div style="background:#f8fafc;border-left:3px solid #0f766e;padding:0.45rem 0.75rem;margin-bottom:0.45rem;border-radius:0 6px 6px 0;font-size:0.87rem;">
<strong style="font-family:monospace;color:#0e7490;"><?= htmlspecialchars((string)($ann['term'] ?? '')) ?></strong>
<?php if (!empty($ann['explanation'])): ?> — <?= htmlspecialchars((string)$ann['explanation']) ?><?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (!empty($output['disclaimer'])): ?>
<p style="font-size:0.85rem;color:#64748b;margin-top:1rem;"><em><?= htmlspecialchars((string)$output['disclaimer']) ?></em></p>
<?php endif; ?>
<?php elseif ($toolSlug === 'legal-analysis'): ?>
<?php if (!empty($output['overall_assessment'])): ?>
<div class="cr-block">
@@ -429,6 +456,7 @@ function crField(string $label, string $value): string {
'redact': '/redact.php',
'transcribe': '/transcribe.php',
'legal-analysis':'/legal-analysis.php',
'translate': '/translate.php',
}[tool] || '/dashboard.php';
window.location.href = path + '?rerun=' + <?= (int)$result['id'] ?>;
});