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
+4
View File
@@ -34,6 +34,7 @@ final class CaseResults
'redact',
'transcribe',
'legal-analysis',
'translate',
];
/** True when the user is on a tier that gets saved results (Plus, Pro, or active Plus trial). */
@@ -240,6 +241,7 @@ final class CaseResults
'redact' => 'Anonymisering',
'transcribe' => 'Transkripsjon',
'legal-analysis' => 'Juridisk analyse',
'translate' => 'Oversettelse',
][$tool] ?? ucfirst($tool);
}
@@ -258,6 +260,7 @@ final class CaseResults
'redact' => '🖊️',
'transcribe' => '🎙️',
'legal-analysis' => '⚖️🇳🇴',
'translate' => '🌐',
][$tool] ?? '📄';
}
@@ -276,6 +279,7 @@ final class CaseResults
'redact' => [$input['text'] ?? null],
'transcribe' => [$input['filename'] ?? null],
'legal-analysis' => [$input['doc_type'] ?? null, $input['text'] ?? null],
'translate' => [$input['source_lang'] ?? null, $input['target_lang'] ?? null, $input['text'] ?? null],
default => [$input['title'] ?? null, $input['query'] ?? null, $input['text'] ?? null],
};
foreach ($candidates as $c) {
+1
View File
@@ -38,6 +38,7 @@ final class FreeTier
'transcribe' => 2,
'discrepancy' => 4,
'korrespond' => 3,
'translate' => 2,
];
/** Monthly credit allowance per tier. */
+98 -1
View File
@@ -435,6 +435,29 @@ function dbnToolsTranslations(): array
'la_addon_button' => '⚖️🇳🇴 Run deep legal analysis on this text',
'la_addon_button_busy' => 'Running deep legal analysis…',
'la_addon_section' => 'Deep Legal Analysis',
'lt_source_label' => 'Source language',
'lt_target_label' => 'Translate to',
'lt_doc_type_label' => 'Document type',
'lt_run_button' => 'Translate document',
'lt_run_button_busy' => 'Translating…',
'lt_input_label' => 'Paste text to translate',
'lt_input_hint' => '(optional if uploading files)',
'lt_input_placeholder' => 'Paste Norwegian legal text here…',
'lt_translating_status' => 'Translating…',
'lt_ready_title' => 'Ready to translate',
'lt_ready_intro' => 'Upload a PDF, DOCX or TXT, or paste text below.',
'lt_result_title' => 'Translation',
'lt_annotations_title' => 'Legal term notes',
'lt_copy_button' => 'Copy translation',
'lt_copy_done' => 'Copied!',
'lt_need_input' => 'Please paste text or upload a file.',
'lt_error_prefix' => 'Error',
'lt_server_returned' => 'Server returned',
'lt_extracting_files' => 'Extracting text from {n} file(s)…',
'lt_engine_hint' => 'Engine: Azure GPT-4o · Legal documents are processed in memory and never stored.',
'lt_same_lang_error' => 'Source and target languages must be different.',
'lt_disclaimer' => 'This is an AI-assisted translation. Always verify with a qualified legal interpreter for official use.',
],
'no' => [
'meta_title' => 'Do Better Norge - juridiske AI-verktøy',
@@ -802,6 +825,29 @@ function dbnToolsTranslations(): array
'la_addon_button' => '⚖️🇳🇴 Kjør dyp juridisk analyse på denne teksten',
'la_addon_button_busy' => 'Kjører dyp juridisk analyse…',
'la_addon_section' => 'Dyp juridisk analyse',
'lt_source_label' => 'Kildespråk',
'lt_target_label' => 'Oversett til',
'lt_doc_type_label' => 'Dokumenttype',
'lt_run_button' => 'Oversett dokument',
'lt_run_button_busy' => 'Oversetter…',
'lt_input_label' => 'Lim inn tekst som skal oversettes',
'lt_input_hint' => '(valgfritt ved filopplasting)',
'lt_input_placeholder' => 'Lim inn norsk juridisk tekst her…',
'lt_translating_status' => 'Oversetter…',
'lt_ready_title' => 'Klar til å oversette',
'lt_ready_intro' => 'Last opp PDF, DOCX eller TXT, eller lim inn tekst nedenfor.',
'lt_result_title' => 'Oversettelse',
'lt_annotations_title' => 'Juridiske termer',
'lt_copy_button' => 'Kopier oversettelse',
'lt_copy_done' => 'Kopiert!',
'lt_need_input' => 'Lim inn tekst eller last opp en fil.',
'lt_error_prefix' => 'Feil',
'lt_server_returned' => 'Serveren svarte',
'lt_extracting_files' => 'Henter tekst fra {n} fil(er)…',
'lt_engine_hint' => 'Motor: Azure GPT-4o · Juridiske dokumenter behandles i minnet og lagres aldri.',
'lt_same_lang_error' => 'Kilde- og målspråk må være forskjellige.',
'lt_disclaimer' => 'Dette er en AI-assistert oversettelse. Verifiser alltid med en kvalifisert juridisk tolk til offisielt bruk.',
],
'uk' => [
'meta_title' => 'Do Better Norge - юридичні AI інструменти',
@@ -1169,6 +1215,29 @@ function dbnToolsTranslations(): array
'la_addon_button' => '⚖️🇳🇴 Запустити глибокий юридичний аналіз цього тексту',
'la_addon_button_busy' => 'Виконується глибокий юридичний аналіз…',
'la_addon_section' => 'Глибокий юридичний аналіз',
'lt_source_label' => 'Мова оригіналу',
'lt_target_label' => 'Перекласти на',
'lt_doc_type_label' => 'Тип документу',
'lt_run_button' => 'Перекласти документ',
'lt_run_button_busy' => 'Перекладаю…',
'lt_input_label' => 'Вставте текст для перекладу',
'lt_input_hint' => '(необов\'язково при завантаженні)',
'lt_input_placeholder' => 'Вставте норвезький юридичний текст тут…',
'lt_translating_status' => 'Перекладаю…',
'lt_ready_title' => 'Готовий до перекладу',
'lt_ready_intro' => 'Завантажте PDF, DOCX або TXT, або вставте текст нижче.',
'lt_result_title' => 'Переклад',
'lt_annotations_title' => 'Юридичні терміни',
'lt_copy_button' => 'Копіювати переклад',
'lt_copy_done' => 'Скопійовано!',
'lt_need_input' => 'Будь ласка, вставте текст або завантажте файл.',
'lt_error_prefix' => 'Помилка',
'lt_server_returned' => 'Сервер повернув',
'lt_extracting_files' => 'Витягую текст з {n} файл(ів)…',
'lt_engine_hint' => 'Механізм: Azure GPT-4o · Юридичні документи обробляються в пам\'яті та не зберігаються.',
'lt_same_lang_error' => 'Мова оригіналу та мова перекладу повинні бути різними.',
'lt_disclaimer' => 'Це переклад за допомогою штучного інтелекту. Завжди перевіряйте з кваліфікованим юридичним перекладачем для офіційного використання.',
],
'pl' => [
'meta_title' => 'Do Better Norge - prawne narzędzia AI',
@@ -1536,6 +1605,29 @@ function dbnToolsTranslations(): array
'la_addon_button' => '⚖️🇳🇴 Uruchom głęboką analizę prawną tego tekstu',
'la_addon_button_busy' => 'Trwa głęboka analiza prawna…',
'la_addon_section' => 'Głęboka analiza prawna',
'lt_source_label' => 'Język źródłowy',
'lt_target_label' => 'Przetłumacz na',
'lt_doc_type_label' => 'Typ dokumentu',
'lt_run_button' => 'Przetłumacz dokument',
'lt_run_button_busy' => 'Tłumaczę…',
'lt_input_label' => 'Wklej tekst do tłumaczenia',
'lt_input_hint' => '(opcjonalne przy wgrywaniu pliku)',
'lt_input_placeholder' => 'Wklej tu norweski tekst prawny…',
'lt_translating_status' => 'Tłumaczę…',
'lt_ready_title' => 'Gotowy do tłumaczenia',
'lt_ready_intro' => 'Prześlij PDF, DOCX lub TXT, lub wklej tekst poniżej.',
'lt_result_title' => 'Tłumaczenie',
'lt_annotations_title' => 'Terminy prawne',
'lt_copy_button' => 'Skopiuj tłumaczenie',
'lt_copy_done' => 'Skopiowano!',
'lt_need_input' => 'Wklej tekst lub prześlij plik.',
'lt_error_prefix' => 'Błąd',
'lt_server_returned' => 'Serwer zwrócił',
'lt_extracting_files' => 'Wyodrębniam tekst z {n} plik(ów)…',
'lt_engine_hint' => 'Silnik: Azure GPT-4o · Dokumenty prawne są przetwarzane w pamięci i nigdy nie zapisywane.',
'lt_same_lang_error' => 'Języki źródłowy i docelowy muszą być różne.',
'lt_disclaimer' => 'To jest tłumaczenie wspomagane AI. Zawsze weryfikuj z wykwalifikowanym tłumaczem prawnym do oficjalnego użytku.',
],
];
}
@@ -1679,6 +1771,7 @@ function dbnToolsLaunchedTools(?string $language = null): array
'discrepancy' => ['Discrepancy Finder', 'Document comparison', 'Upload two versions of a Barnevernet document and find contradictions, deleted facts, and new allegations.', 'Cross-document AI'],
'corpus' => ['Corpus', 'Legal knowledge base', 'Inspect indexed sources, corpus health, legal categories, and retrieval behavior.', '~220 K passages'],
'citations' => ['Citations', 'Citation graph', 'Browse the legal citation graph — what a statute cites, what cites it, and what implements or amends it.', 'Graph topology'],
'translate' => ['Translate', 'Legal translation', 'Translate Barnevernet letters and legal documents into your language with legal-terminology annotations.', 'Azure · GPT-4o'],
],
'no' => [
'transcribe' => ['Transkriber', 'Lyd og møter', 'Gjør lyd eller video om til tekst med talerinndeling og juridisk ordforråd.', 'Whisper / GPU'],
@@ -1693,6 +1786,7 @@ function dbnToolsLaunchedTools(?string $language = null): array
'discrepancy' => ['Avviksfinner', 'Dokumentsammenligning', 'Last opp to versjoner av et barneverndokument og finn motsigelser, slettede fakta og nye påstander.', 'Kryssdokument AI'],
'corpus' => ['Korpus', 'Juridisk kunnskapsbase', 'Se indekserte kilder, korpushelse, juridiske kategorier og søkeoppsett.', '~220 K utdrag'],
'citations' => ['Siteringer', 'Siteringsgraf', 'Utforsk siteringsgrafen — hva et dokument siterer, hva som siterer det, og hva som implementerer det.', 'Grafstruktur'],
'translate' => ['Oversett', 'Juridisk oversettelse', 'Oversett Barnevernet-brev og juridiske dokumenter til ditt språk med juridisk terminologi.', 'Azure · GPT-4o'],
],
'uk' => [
'transcribe' => ['Транскрипція', 'Аудіо та зустрічі', 'Перетворюйте аудіо або відео на текст із розділенням мовців і юридичною лексикою.', 'Whisper / GPU'],
@@ -1707,6 +1801,7 @@ function dbnToolsLaunchedTools(?string $language = null): array
'discrepancy' => ['Пошук розбіжностей', 'Порівняння документів', 'Завантажте дві версії документа Barnevernet і знайдіть суперечності, видалені факти та нові твердження.', 'Міждокументний AI'],
'corpus' => ['Корпус', 'Юридична база знань', 'Переглядайте індексовані джерела, стан корпусу, категорії та поведінку пошуку.', '~220 тис. уривків'],
'citations' => ['Граф цитувань', 'Мережа посилань', 'Граф правових посилань — що цитує документ, хто цитує його, що його реалізує.', 'Граф-топологія'],
'translate' => ['Перекласти', 'Юридичний переклад', 'Перекладайте листи Barnevernet та юридичні документи на свою мову з юридичними термінами.', 'Azure · GPT-4o'],
],
'pl' => [
'transcribe' => ['Transkrypcja', 'Audio i spotkania', 'Zamień audio lub wideo na tekst z rozdzieleniem mówców i słownictwem prawnym.', 'Whisper / GPU'],
@@ -1721,11 +1816,12 @@ function dbnToolsLaunchedTools(?string $language = null): array
'discrepancy' => ['Wyszukiwacz rozbieżności', 'Porównanie dokumentów', 'Prześlij dwie wersje dokumentu Barnevernet i znajdź sprzeczności, usunięte fakty i nowe zarzuty.', 'AI Między-dokumentowe'],
'corpus' => ['Korpus', 'Prawna baza wiedzy', 'Sprawdzaj indeksowane źródła, stan korpusu, kategorie prawne i działanie wyszukiwania.', '~220 tys. fragmentów'],
'citations' => ['Graf cytowań', 'Sieć cytowań', 'Przeglądaj sieć cytowań — co cytuje dokument, kto go cytuje i co go implementuje.', 'Topologia grafu'],
'translate' => ['Tłumacz', 'Tłumaczenie prawne', 'Tłumacz listy Barnevernet i dokumenty prawne na swój język z adnotacjami terminologicznymi.', 'Azure · GPT-4o'],
],
];
$selected = $copy[$language] ?? $copy['en'];
$order = ['transcribe', 'timeline', 'redact', 'summarize', 'legal-analysis', 'korrespond', 'barnevernet', 'advocate', 'deep-research', 'discrepancy', 'corpus', 'citations'];
$order = ['transcribe', 'timeline', 'redact', 'summarize', 'legal-analysis', 'korrespond', 'barnevernet', 'advocate', 'deep-research', 'discrepancy', 'corpus', 'citations', 'translate'];
$icons = [
'transcribe' => 'TR',
'timeline' => 'TL',
@@ -1739,6 +1835,7 @@ function dbnToolsLaunchedTools(?string $language = null): array
'discrepancy' => 'DC',
'corpus' => 'KB',
'citations' => 'CIT',
'translate' => 'TX',
];
$out = [];
foreach ($order as $slug) {