1bfafa9908
- Replace all hardcoded English strings in mcp.php with dbnToolsT() calls - Add 44 MCP UI chrome translation keys to includes/i18n.php (en/no/uk/pl) - Generate includes/mcp-tool-translations.php with tool names, descriptions, and parameter docs translated into Norwegian, Ukrainian, and Polish via Azure OpenAI - Create mcp-tool.php: parameterized detail page (?tool=dbn.slug) with parameter table, example request/response JSON, and privacy section, all localized - Add "View details →" links on tool cards in mcp.php (shown on expand) - Add translations/mcp-chrome.php and scripts/generate-mcp-translations.php Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
464 lines
25 KiB
PHP
464 lines
25 KiB
PHP
<?php
|
||
/**
|
||
* generate-mcp-translations.php
|
||
* One-shot CLI script: php scripts/generate-mcp-translations.php
|
||
*
|
||
* Produces two files:
|
||
* 1. translations/mcp-chrome.php — UI chrome strings for mcp.php + mcp-tool.php
|
||
* 2. includes/mcp-tool-translations.php — tool names, descriptions, param docs in all 4 languages
|
||
*/
|
||
declare(strict_types=1);
|
||
|
||
define('SCRIPT_MODE', true);
|
||
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// Azure config (same pattern as generate-page-translations.php)
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
$azConfig = [
|
||
'endpoint' => rtrim((string)dbnToolsEnv('DBN_AZURE_OPENAI_ENDPOINT', ''), '/'),
|
||
'api_key' => (string)dbnToolsEnv('DBN_AZURE_OPENAI_API_KEY', ''),
|
||
'api_version' => (string)dbnToolsEnv('DBN_AZURE_OPENAI_API_VERSION', '2024-02-01'),
|
||
'chat_deployment' => (string)dbnToolsEnv('DBN_AZURE_OPENAI_CHAT_DEPLOYMENT', 'gpt-4o-mini'),
|
||
];
|
||
|
||
function azureChat(array $config, array $messages, array $options = []): array
|
||
{
|
||
$url = $config['endpoint']
|
||
. '/openai/deployments/'
|
||
. rawurlencode($config['chat_deployment'])
|
||
. '/chat/completions?api-version='
|
||
. rawurlencode($config['api_version']);
|
||
|
||
$payload = json_encode(array_filter([
|
||
'messages' => $messages,
|
||
'temperature' => $options['temperature'] ?? 0.1,
|
||
'max_tokens' => $options['max_tokens'] ?? 4096,
|
||
'response_format' => isset($options['json']) && $options['json']
|
||
? ['type' => 'json_object'] : null,
|
||
], fn($v) => $v !== null), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => $payload,
|
||
CURLOPT_HTTPHEADER => [
|
||
'Content-Type: application/json',
|
||
'api-key: ' . $config['api_key'],
|
||
],
|
||
CURLOPT_TIMEOUT => (int)($options['timeout'] ?? 120),
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_SSL_VERIFYHOST => false,
|
||
]);
|
||
|
||
$body = curl_exec($ch);
|
||
$code = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||
$err = curl_error($ch);
|
||
curl_close($ch);
|
||
|
||
if ($body === false) throw new RuntimeException("Azure curl failed: $err");
|
||
$decoded = json_decode($body, true);
|
||
if (!is_array($decoded)) throw new RuntimeException("Azure returned non-JSON (HTTP $code)");
|
||
if ($code < 200 || $code >= 300) {
|
||
throw new RuntimeException('Azure error: ' . ($decoded['error']['message'] ?? "HTTP $code"));
|
||
}
|
||
return $decoded;
|
||
}
|
||
|
||
$languages = [
|
||
'no' => 'Norwegian (Norsk bokmål)',
|
||
'uk' => 'Ukrainian',
|
||
'pl' => 'Polish',
|
||
];
|
||
|
||
$systemPrompt = <<<'PROMPT'
|
||
You are a professional legal translator specializing in Norwegian family law and child welfare.
|
||
Translate all JSON string values to {LANG}.
|
||
|
||
PRESERVE AS-IS:
|
||
- Norwegian institution names: Barnevernet, Statsforvalteren, Bufdir, Fylkesnemnda, NAV, Tingretten
|
||
- Norwegian legal act names: forvaltningsloven, barnevernsloven, opplæringslova, EMK, fvl
|
||
- Norwegian legal terms: klage, vedtak, saksnummer, akutt, omsorgsovertakelse, tiltaksplan
|
||
- Technical terms: MCP, DBN, dbn.tool_name, JSON, Bearer token, npx, stdio
|
||
- Brand names: Do Better Norge, Claude, Cursor, VS Code, Windsurf, Copilot
|
||
- Code snippets, URLs, env var names like DBN_MCP_TOKEN
|
||
- HTML entities and tags exactly as they appear
|
||
- The symbol ← and →
|
||
|
||
Return a JSON object with EXACTLY the same keys as the input. Translate only the string values.
|
||
Use formal/professional register appropriate for a legal tools platform.
|
||
PROMPT;
|
||
|
||
function translateBatch(array $azConfig, string $langName, string $systemPrompt, array $batch): array
|
||
{
|
||
$prompt = str_replace('{LANG}', $langName, $systemPrompt);
|
||
$json = json_encode($batch, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||
$response = azureChat($azConfig, [
|
||
['role' => 'system', 'content' => $prompt],
|
||
['role' => 'user', 'content' => $json],
|
||
], ['json' => true, 'max_tokens' => 4096, 'timeout' => 120]);
|
||
|
||
$content = trim((string)($response['choices'][0]['message']['content'] ?? ''));
|
||
$decoded = json_decode($content, true);
|
||
if (!is_array($decoded)) {
|
||
echo " [WARN] JSON parse failed, trying extraction...\n";
|
||
if (preg_match('/\{.*\}/s', $content, $m)) {
|
||
$decoded = json_decode($m[0], true);
|
||
}
|
||
}
|
||
if (!is_array($decoded)) {
|
||
echo " [ERROR] Could not parse response, using English fallback\n";
|
||
return $batch;
|
||
}
|
||
foreach ($batch as $k => $v) {
|
||
if (!isset($decoded[$k])) $decoded[$k] = $v;
|
||
}
|
||
return $decoded;
|
||
}
|
||
|
||
function writePhpFile(string $path, array $data, string $comment): void
|
||
{
|
||
$php = "<?php\n// $comment\n// DO NOT EDIT MANUALLY — regenerate with scripts/generate-mcp-translations.php\nreturn ";
|
||
$php .= var_export($data, true);
|
||
$php .= ";\n";
|
||
file_put_contents($path, $php);
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// Part 1 — MCP UI chrome strings
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
$chromeEn = [
|
||
// mcp.php
|
||
'mcp_page_title' => 'MCP — Do Better Norge',
|
||
'mcp_meta_desc' => 'Connect Claude, Cursor, and other AI tools to all 19 DBN legal preparation tools via MCP.',
|
||
'mcp_hero_badge' => '✦ Plus & Pro',
|
||
'mcp_hero_h1' => 'Use DBN tools from Claude, Cursor & Copilot',
|
||
'mcp_hero_sub' => 'Connect any MCP client to all 19 Do Better Norge tools — transcription, legal analysis, timelines, redaction, and more.',
|
||
'mcp_token_section_title' => 'Your MCP token',
|
||
'mcp_gate_guest_p' => 'Sign in to create your personal MCP token. Available to Plus and Pro members.',
|
||
'mcp_gate_guest_btn' => 'Sign in',
|
||
'mcp_gate_free_p' => 'MCP access is available on Plus and Pro plans. Upgrade to connect your AI tools.',
|
||
'mcp_gate_free_btn' => 'Upgrade plan',
|
||
'mcp_token_hint' => 'Tokens are shown once at creation. Create one per client (Claude, Cursor, VS Code…).',
|
||
'mcp_token_create_btn' => 'Create token',
|
||
'mcp_token_reveal_label' => 'Copy this token now — it will not be shown again:',
|
||
'mcp_token_copy_btn' => 'Copy token',
|
||
'mcp_token_no_tokens' => 'No MCP tokens yet.',
|
||
'mcp_token_active' => 'Active',
|
||
'mcp_token_revoked' => 'Revoked',
|
||
'mcp_token_never_used' => 'Never used',
|
||
'mcp_token_last_used' => 'Last used',
|
||
'mcp_token_revoke_btn' => 'Revoke',
|
||
'mcp_config_title' => 'Client configuration',
|
||
'mcp_config_hint' => 'Paste your token into the config below after creating it above.',
|
||
'mcp_config_token_filled' => 'Token auto-filled.',
|
||
'mcp_config_run_terminal' => 'Run in your terminal:',
|
||
'mcp_test_btn' => 'Test connection',
|
||
'mcp_test_no_token' => 'Create a token first.',
|
||
'mcp_test_testing' => 'Testing…',
|
||
'mcp_tools_title' => 'Available tools',
|
||
'mcp_tools_sub' => 'All tools run on your Plus or Pro plan credits. Click a card for full technical details.',
|
||
'mcp_tools_param_req_hint' => 'Purple = required',
|
||
'mcp_tools_view_details' => 'View details →',
|
||
// privacy section
|
||
'mcp_privacy_title' => 'Privacy',
|
||
'mcp_privacy_text' => 'Process-and-forget by default. All tool calls process your text in memory and return results to your AI client. Nothing is saved to My Case unless you explicitly call dbn.save_to_case.',
|
||
'mcp_privacy_legal' => 'Tools provide legal preparation support, not final legal advice. Results are for informational purposes and should be reviewed by a qualified legal professional.',
|
||
// mcp-tool.php
|
||
'mcp_tool_back' => '← Back to MCP setup',
|
||
'mcp_tool_params_title' => 'Parameters',
|
||
'mcp_tool_no_params' => 'This tool takes no input parameters.',
|
||
'mcp_tool_col_param' => 'Parameter',
|
||
'mcp_tool_col_type' => 'Type',
|
||
'mcp_tool_col_required' => 'Required',
|
||
'mcp_tool_col_desc' => 'Description',
|
||
'mcp_tool_example_req' => 'Example request',
|
||
'mcp_tool_example_resp' => 'Example response',
|
||
'mcp_tool_connect_title' => 'Connect',
|
||
'mcp_tool_connect_text' => 'Create your MCP token on the setup page and use it with any supported client.',
|
||
'mcp_tool_setup_link' => 'Set up MCP →',
|
||
'mcp_tool_yes' => 'Yes',
|
||
'mcp_tool_no' => 'No',
|
||
];
|
||
|
||
echo "\n=== Part 1: Translating MCP UI chrome strings ===\n";
|
||
$chromeTranslations = ['en' => $chromeEn];
|
||
$batchSize = 20;
|
||
$keys = array_keys($chromeEn);
|
||
$batches = array_chunk($keys, $batchSize);
|
||
|
||
foreach ($languages as $langCode => $langName) {
|
||
echo " Language: $langName ($langCode)\n";
|
||
$langResult = [];
|
||
foreach ($batches as $idx => $batchKeys) {
|
||
$batchNum = $idx + 1;
|
||
echo " Batch $batchNum/" . count($batches) . " (" . count($batchKeys) . " strings)...\n";
|
||
$batchInput = [];
|
||
foreach ($batchKeys as $k) $batchInput[$k] = $chromeEn[$k];
|
||
$langResult = array_merge($langResult, translateBatch($azConfig, $langName, $systemPrompt, $batchInput));
|
||
}
|
||
$chromeTranslations[$langCode] = $langResult;
|
||
}
|
||
|
||
$chromeOutPath = dirname(__DIR__) . '/translations/mcp-chrome.php';
|
||
writePhpFile($chromeOutPath, $chromeTranslations, 'MCP UI chrome translations — mcp.php + mcp-tool.php');
|
||
echo " Written: $chromeOutPath\n";
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// Part 2 — MCP tool content (display_name, description, param descriptions)
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
// English source for all tool content — param descriptions are authored here
|
||
// since DbnMcpRuntime.php params mostly lack description fields.
|
||
$toolsEn = [
|
||
'dbn.search_legal' => [
|
||
'display_name' => 'Search DBN legal corpus',
|
||
'description' => 'Search the DBN Norwegian family-law corpus.',
|
||
'params' => [
|
||
'query' => 'The search query (minimum 3 characters). Enter a legal topic, keyword, or question.',
|
||
'language' => 'Response language: en, no, uk, pl, or auto (detect from input).',
|
||
'limit' => 'Maximum number of results to return (1–10).',
|
||
'corpus_scope' => 'Which corpus to search: shared (public legal corpus), private (your uploaded documents), or both.',
|
||
],
|
||
],
|
||
'dbn.ask' => [
|
||
'display_name' => 'Ask a legal question',
|
||
'description' => 'Answer a legal preparation question with source-grounded DBN context.',
|
||
'params' => [
|
||
'question' => 'The legal question to answer (minimum 5 characters).',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
'use_case_context' => 'Include context from your Case Workbench session (true/false).',
|
||
],
|
||
],
|
||
'dbn.summarize' => [
|
||
'display_name' => 'Summarize document',
|
||
'description' => 'Summarize pasted case text with optional legal-corpus enrichment.',
|
||
'params' => [
|
||
'text' => 'The text to summarize.',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
'use_legal_corpus' => 'Enrich the summary with relevant legal corpus passages (true/false).',
|
||
'use_case_context' => 'Include context from your Case Workbench session (true/false).',
|
||
],
|
||
],
|
||
'dbn.timeline' => [
|
||
'display_name' => 'Extract timeline',
|
||
'description' => 'Extract dates, hearings, milestones, and deadlines from case text.',
|
||
'params' => [
|
||
'text' => 'The case text to extract dates from.',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
'focus' => 'What to extract: all (every date), deadlines (appeal windows and filing deadlines), hearings (tribunal and court dates), cps (Barnevernet milestones).',
|
||
'use_case_context' => 'Include context from your Case Workbench session (true/false).',
|
||
],
|
||
],
|
||
'dbn.redact' => [
|
||
'display_name' => 'Redact private data',
|
||
'description' => 'Remove or pseudonymize names, IDs, phone numbers, addresses, and places.',
|
||
'params' => [
|
||
'text' => 'The text to redact.',
|
||
'language' => 'Language of the input text: en, no, uk, pl, or auto.',
|
||
'mode' => 'Redaction scope: standard (names, IDs, phones) or strict (also addresses, locations, institutions).',
|
||
'output_format' => 'Replacement style: contextual ([PERSON A]), generic (█████), or pseudonym (consistent invented names).',
|
||
],
|
||
],
|
||
'dbn.translate' => [
|
||
'display_name' => 'Translate legal document',
|
||
'description' => 'Translate Norwegian family-law text with legal terminology annotations.',
|
||
'params' => [
|
||
'text' => 'The text to translate.',
|
||
'source_lang' => 'Language of the input text. Use auto to detect automatically.',
|
||
'target_lang' => 'Language to translate into (required): en, no, uk, or pl.',
|
||
'doc_type' => 'Document type hint for legal terminology: auto, barnevernet, adopsjon, emergency, samvaer, fylkesnemnd, or other.',
|
||
],
|
||
],
|
||
'dbn.legal_analysis' => [
|
||
'display_name' => 'Legal analysis',
|
||
'description' => 'Extract legal issues from a document and answer each with DBN legal context.',
|
||
'params' => [
|
||
'text' => 'The document text to analyze.',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
'doc_type' => 'Document type hint: auto, barnevernet, adopsjon, emergency, samvaer, fylkesnemnd, or other.',
|
||
],
|
||
],
|
||
'dbn.korrespond' => [
|
||
'display_name' => 'Draft authority correspondence',
|
||
'description' => 'Draft a reply or new letter to Norwegian authorities.',
|
||
'params' => [
|
||
'narrative' => 'Description of your situation — what happened and what you need.',
|
||
'received_text' => 'Text of the letter or decision you received (for reply mode).',
|
||
'recipient_body' => 'The authority you are writing to (e.g. Barnevernet, NAV, Skole, Kommune).',
|
||
'goal' => 'Your legal goal — what you want the letter to achieve.',
|
||
'mode' => 'reply (responding to a received letter) or initiate (starting new correspondence).',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
'use_case_context' => 'Include context from your Case Workbench session (true/false).',
|
||
'force_draft' => 'Skip the clarify gate and draft immediately (true/false).',
|
||
],
|
||
],
|
||
'dbn.barnevernet_analyze' => [
|
||
'display_name' => 'Analyze Barnevernet document',
|
||
'description' => 'Analyze child-welfare documents for red flags and legal issues.',
|
||
'params' => [
|
||
'document_text' => 'Full text of the child welfare document to analyze (required).',
|
||
'filename' => 'Original filename for context (helps identify document type).',
|
||
'advocate_role' => 'Your role in the case: parent, lawyer, guardian ad litem, etc.',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
'use_case_context' => 'Include context from your Case Workbench session (true/false).',
|
||
],
|
||
],
|
||
'dbn.advocate_brief' => [
|
||
'display_name' => 'Create advocate brief',
|
||
'description' => 'Generate a source-grounded brief for a chosen party or role.',
|
||
'params' => [
|
||
'query' => 'Legal question or topic for the brief.',
|
||
'paste_text' => 'Document text to use as the basis for the brief.',
|
||
'advocate_role' => 'The party or role to advocate for (required). E.g. parent, child, Barnevernet.',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
'use_case_context' => 'Include context from your Case Workbench session (true/false).',
|
||
],
|
||
],
|
||
'dbn.deep_research' => [
|
||
'display_name' => 'Deep research',
|
||
'description' => 'Expand a legal question into research angles and synthesize a cited brief.',
|
||
'params' => [
|
||
'query' => 'Legal question to research in depth.',
|
||
'paste_text' => 'Supporting document text to research from.',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
'use_case_context' => 'Include context from your Case Workbench session (true/false).',
|
||
],
|
||
],
|
||
'dbn.discrepancy_find' => [
|
||
'display_name' => 'Find document discrepancies',
|
||
'description' => 'Compare two document versions for contradictions, deletions, and added claims.',
|
||
'params' => [
|
||
'document_a_text' => 'Text of the first document (required).',
|
||
'document_b_text' => 'Text of the second document to compare against the first (required).',
|
||
'filename_a' => 'Filename of the first document (for reference in the report).',
|
||
'filename_b' => 'Filename of the second document (for reference in the report).',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
],
|
||
],
|
||
'dbn.transcribe_audio' => [
|
||
'display_name' => 'Transcribe audio',
|
||
'description' => 'Transcribe an audio file from base64-encoded content or a URL.',
|
||
'params' => [
|
||
'audio_base64' => 'Base64-encoded audio file content. Use either this or audio_url.',
|
||
'audio_url' => 'URL to a publicly accessible audio file. Use either this or audio_base64.',
|
||
'filename' => 'Original filename with extension (e.g. recording.mp3) — helps set the correct audio format.',
|
||
'language' => 'Language spoken in the audio (e.g. no, en, uk). Leave blank for auto-detection.',
|
||
'diarize' => 'Enable speaker diarization — label each segment with a speaker identifier (true/false).',
|
||
],
|
||
],
|
||
'dbn.corpus_stats' => [
|
||
'display_name' => 'Corpus statistics',
|
||
'description' => 'Return document and chunk counts and active legal sources in the DBN corpus.',
|
||
'params' => [],
|
||
],
|
||
'dbn.list_documents' => [
|
||
'display_name' => 'List corpus documents',
|
||
'description' => 'List DBN legal corpus documents with optional filters.',
|
||
'params' => [
|
||
'category' => 'Filter by document category (e.g. barnevernet, statsforvalter, echr).',
|
||
'title' => 'Filter by title — partial match.',
|
||
'limit' => 'Maximum number of documents to return (1–50, default 20).',
|
||
'offset' => 'Number of documents to skip (for pagination).',
|
||
],
|
||
],
|
||
'dbn.get_document' => [
|
||
'display_name' => 'Get document chunks',
|
||
'description' => 'Fetch a document and its text chunks by numeric document ID.',
|
||
'params' => [
|
||
'document_id' => 'The numeric ID of the document to retrieve (required).',
|
||
],
|
||
],
|
||
'dbn.citation_graph' => [
|
||
'display_name' => 'Explore citation graph',
|
||
'description' => 'Explore cites, cited-by, implementation, or chain relationships.',
|
||
'params' => [
|
||
'doc_id' => 'The numeric ID of the document to explore (required).',
|
||
'action' => 'Relationship to traverse: cites, cited_by, implements, or chain (full citation path).',
|
||
'depth' => 'How many levels of relationships to explore (1–3, default 1).',
|
||
],
|
||
],
|
||
'dbn.case_workbench_plan' => [
|
||
'display_name' => 'Plan next case step',
|
||
'description' => 'Create a stateless legal preparation plan based on your situation.',
|
||
'params' => [
|
||
'situation' => 'Describe your current legal situation and what you need help with (required).',
|
||
'goal' => 'Your desired outcome or legal goal.',
|
||
'deadline' => 'Any relevant deadline (date or description, e.g. "3 weeks").',
|
||
'language' => 'Response language: en, no, uk, pl, or auto.',
|
||
],
|
||
],
|
||
'dbn.save_to_case' => [
|
||
'display_name' => 'Save result to My Case',
|
||
'description' => 'Explicitly save a prior MCP tool result to the user case record.',
|
||
'params' => [
|
||
'tool' => 'The MCP tool slug whose result you are saving (required).',
|
||
'title' => 'A descriptive title for this saved result.',
|
||
'input_payload' => 'The input parameters used to generate the result (required).',
|
||
'output_payload' => 'The tool result to save (required).',
|
||
'meta' => 'Optional metadata (e.g. notes, tags, source reference).',
|
||
],
|
||
],
|
||
];
|
||
|
||
echo "\n=== Part 2: Translating MCP tool content ===\n";
|
||
|
||
// Flatten all tool strings for translation (display_name, description, params)
|
||
// Key format: {short_slug}__name, {short_slug}__desc, {short_slug}__p__{param}
|
||
$flatSource = [];
|
||
$slugMap = []; // short_slug => full_slug
|
||
|
||
foreach ($toolsEn as $fullSlug => $toolData) {
|
||
$shortSlug = str_replace('dbn.', '', $fullSlug);
|
||
$slugMap[$shortSlug] = $fullSlug;
|
||
$flatSource[$shortSlug . '__name'] = $toolData['display_name'];
|
||
$flatSource[$shortSlug . '__desc'] = $toolData['description'];
|
||
foreach ($toolData['params'] as $param => $desc) {
|
||
$flatSource[$shortSlug . '__p__' . $param] = $desc;
|
||
}
|
||
}
|
||
|
||
// Translate in batches of 20
|
||
$flatKeys = array_keys($flatSource);
|
||
$batches = array_chunk($flatKeys, 20);
|
||
$flatTranslations = ['en' => $flatSource];
|
||
|
||
foreach ($languages as $langCode => $langName) {
|
||
echo " Language: $langName ($langCode)\n";
|
||
$langResult = [];
|
||
foreach ($batches as $idx => $batchKeys) {
|
||
$batchNum = $idx + 1;
|
||
echo " Batch $batchNum/" . count($batches) . " (" . count($batchKeys) . " strings)...\n";
|
||
$batchInput = [];
|
||
foreach ($batchKeys as $k) $batchInput[$k] = $flatSource[$k];
|
||
$langResult = array_merge($langResult, translateBatch($azConfig, $langName, $systemPrompt, $batchInput));
|
||
}
|
||
$flatTranslations[$langCode] = $langResult;
|
||
}
|
||
|
||
// Reconstruct nested structure: [full_slug][lang] => ['display_name', 'description', 'params']
|
||
$toolTranslations = [];
|
||
foreach ($toolsEn as $fullSlug => $toolData) {
|
||
$shortSlug = str_replace('dbn.', '', $fullSlug);
|
||
foreach (['en', 'no', 'uk', 'pl'] as $lc) {
|
||
$toolTranslations[$fullSlug][$lc] = [
|
||
'display_name' => $flatTranslations[$lc][$shortSlug . '__name'] ?? $toolData['display_name'],
|
||
'description' => $flatTranslations[$lc][$shortSlug . '__desc'] ?? $toolData['description'],
|
||
'params' => [],
|
||
];
|
||
foreach (array_keys($toolData['params']) as $param) {
|
||
$toolTranslations[$fullSlug][$lc]['params'][$param] =
|
||
$flatTranslations[$lc][$shortSlug . '__p__' . $param] ?? $toolData['params'][$param];
|
||
}
|
||
}
|
||
}
|
||
|
||
$toolOutPath = dirname(__DIR__) . '/includes/mcp-tool-translations.php';
|
||
writePhpFile($toolOutPath, $toolTranslations, 'MCP tool translations — display_name, description, param descriptions');
|
||
echo " Written: $toolOutPath\n";
|
||
|
||
echo "\n✓ Done. Next steps:\n";
|
||
echo " 1. Copy translations from translations/mcp-chrome.php into includes/i18n.php\n";
|
||
echo " 2. The tool translations are ready in includes/mcp-tool-translations.php\n";
|