-
}
- style={{ marginBottom: '20px' }}
- >
-
- {tSync('apex.coming_soon.message', 'The Apex functionality is currently under development and will be available in the next release. This will include Apex code execution, saved Apex management, and Salesforce metadata integration.')}
-
-
-
+
{
onChange={(e) => setState(prev => ({ ...prev, searchTerm: e.currentTarget.value }))}
leftSection={ }
size="sm"
- disabled
/>
@@ -685,7 +851,6 @@ export const ApexTab: React.FC = () => {
setState(prev => ({ ...prev, filterStatus: e.target.value }))}
- disabled
style={{
padding: '8px 12px',
border: '1px solid #ced4da',
@@ -704,7 +869,6 @@ export const ApexTab: React.FC = () => {
setState(prev => ({ ...prev, filterCodeType: e.target.value }))}
- disabled
style={{
padding: '8px 12px',
border: '1px solid #ced4da',
@@ -732,7 +896,6 @@ export const ApexTab: React.FC = () => {
size="xs"
onClick={loadSavedApexData}
loading={state.isLoading}
- disabled
className="query-tab-page-button"
style={{
padding: '6px 12px',
@@ -749,10 +912,9 @@ export const ApexTab: React.FC = () => {
leftSection={ }
size="xs"
onClick={openCreateModal}
- disabled
className="query-tab-save-button"
- style={{
- padding: '6px 12px',
+ style={{
+ padding: '6px 12px',
minHeight: '28px',
fontSize: '11px',
fontWeight: 600,
@@ -762,55 +924,24 @@ export const ApexTab: React.FC = () => {
>
{tSync('saved_apex.actions.create', 'Create Apex')}
- }
- variant="light"
- size="xs"
- disabled
- className="query-tab-page-button"
- style={{
- padding: '6px 12px',
- minHeight: '28px',
- fontSize: '11px',
- fontWeight: 600,
- borderRadius: '6px',
- transition: 'all 0.2s ease'
- }}
- >
- {tSync('saved_apex.actions.execute')}
-
- }
- variant="light"
- size="xs"
- disabled
- className="query-tab-page-button"
- style={{
- padding: '6px 12px',
- minHeight: '28px',
- fontSize: '11px',
- fontWeight: 600,
- borderRadius: '6px',
- transition: 'all 0.2s ease'
- }}
- >
- {tSync('common.actions.export')}
-
-
+
setState(prev => ({ ...prev, activeTab: value as any || 'saved' }))}>
- } disabled>
+ }>
{tSync('saved_apex.tabs.all', { count: filteredSavedApex.length })}
- } disabled>
+ }>
{tSync('saved_apex.filter.type.class', { count: apexClasses.length })}
- } disabled>
+ }>
{tSync('saved_apex.filter.type.trigger', { count: apexTriggers.length })}
+ }>
+ Test Runner
+
@@ -819,107 +950,166 @@ export const ApexTab: React.FC = () => {
{filteredSavedApex.map(renderSavedApexItem)}
- {state.showEditPanel && editingApex && (
+ {state.showEditPanel && (editingApex || state.selectedClass || state.selectedTrigger) && (
-
Edit Apex Code
+
+ {editingApex ? 'Edit Apex Code' : state.selectedClass ? `View Class: ${state.selectedClass.name}` : `View Trigger: ${state.selectedTrigger?.name}`}
+
setState(prev => ({ ...prev, showEditPanel: false }))}
+ onClick={() => setState(prev => ({ ...prev, showEditPanel: false, selectedClass: null, selectedTrigger: null }))}
>
-
-
- setFormData({ ...formData, name: e.target.value })}
- required
- style={{ flex: 1 }}
- />
-
- setFormData({ ...formData, description: e.target.value })}
- style={{ flex: 1 }}
- />
-
- setFormData({ ...formData, tags: e.target.value })}
- style={{ flex: 1 }}
- />
-
-
-
-
- Code Type
- setFormData({ ...formData, code_type: e.target.value as ApexCodeType })}
- style={{
- padding: '8px 12px',
- border: '1px solid #ced4da',
- borderRadius: '6px',
- fontSize: '14px',
- backgroundColor: 'white',
- flex: 1
- }}
- >
- Anonymous
- Class
- Trigger
- Interface
- Enum
- Test Class
-
-
-
- setFormData({ ...formData, is_favorite: e.target.checked })}
- />
-
-
-
-
)}
+
+ {/* Execution Results Panel - Show loading or results */}
+ {state.showExecutionModal && (
+
+
+
+ Apex Execution Results
+
+ {!state.isExecuting && (
+
setState(prev => ({ ...prev, showExecutionModal: false, executionResult: null }))}
+ >
+
+
+ )}
+
+
+
+ {state.isExecuting ? (
+
+
+ Executing Apex code...
+
+ ) : state.executionResult ? (
+
+
+
+ : }
+ >
+ {state.executionResult.success ? 'Execution Successful' : 'Execution Failed'}
+
+
+
+ {state.executionResult.message && (
+ {state.executionResult.message}
+ )}
+
+ {state.executionResult.compile_problem && (
+
+ Compilation Error:
+
+ {state.executionResult.compile_problem}
+
+ {state.executionResult.line && (
+
+ Line {state.executionResult.line}, Column {state.executionResult.column}
+
+ )}
+
+ )}
+
+ {state.executionResult.exception_message && (
+
+ Exception:
+
+ {state.executionResult.exception_message}
+
+
+ )}
+
+ {state.executionResult.limit_exceptions && state.executionResult.limit_exceptions.length > 0 && (
+
+ Governor Limit Warnings:
+
+ {state.executionResult.limit_exceptions.map((limit, index) => (
+ {limit}
+ ))}
+
+
+ )}
+
+
+ Performance Metrics:
+
+ {state.executionResult.execution_time !== undefined && (
+
+ Execution Time: {state.executionResult.execution_time}ms
+
+ )}
+ {state.executionResult.cpu_time !== undefined && (
+
+ CPU Time: {state.executionResult.cpu_time}ms
+
+ )}
+ {state.executionResult.dml_statements !== undefined && (
+
+ DML Statements: {state.executionResult.dml_statements}
+
+ )}
+
+
+
+ {state.executionResult.debug_log && state.executionResult.debug_log.length > 0 && (
+
+
Debug Log:
+
+
+ {state.executionResult.debug_log}
+
+
+
+ )}
+
+
+ ) : null}
+
+
+ )}
+
+ {/* Test Results Panel - Show during edit when test is executed */}
+ {state.showEditPanel && editingApex && state.showTestResultsModal && (
+
+
+
+ Test Results
+
+ {!state.isExecuting && (
+
setState(prev => ({ ...prev, showTestResultsModal: false, executionResult: null }))}
+ >
+
+
+ )}
+
+
+
+ {state.isExecuting ? (
+
+
+ Executing test...
+
+ ) : state.executionResult ? (
+
+
+
+ : }
+ >
+ {state.executionResult.success ? 'Test Passed' : 'Test Failed'}
+
+
+
+ {state.executionResult.message && (
+ {state.executionResult.message}
+ )}
+
+ {state.executionResult.compile_problem && (
+
+ Compilation Error:
+
+ {state.executionResult.compile_problem}
+
+ {state.executionResult.line && (
+
+ Line {state.executionResult.line}, Column {state.executionResult.column}
+
+ )}
+
+ )}
+
+ {state.executionResult.exception_message && (
+
+ Exception:
+
+ {state.executionResult.exception_message}
+
+
+ )}
+
+ {state.executionResult.limit_exceptions && state.executionResult.limit_exceptions.length > 0 && (
+
+ Governor Limit Warnings:
+
+ {state.executionResult.limit_exceptions.map((limit, index) => (
+ {limit}
+ ))}
+
+
+ )}
+
+
+ Performance Metrics:
+
+ {state.executionResult.execution_time !== undefined && (
+
+ Execution Time: {state.executionResult.execution_time}ms
+
+ )}
+ {state.executionResult.cpu_time !== undefined && (
+
+ CPU Time: {state.executionResult.cpu_time}ms
+
+ )}
+ {state.executionResult.dml_statements !== undefined && (
+
+ DML Statements: {state.executionResult.dml_statements}
+
+ )}
+
+
+
+ {state.executionResult.debug_log && state.executionResult.debug_log.length > 0 && (
+
+
Debug Log:
+
+
+ {state.executionResult.debug_log}
+
+
+
+ )}
+
+
+ ) : null}
+
+
+ )}
-
-
-
- Salesforce metadata integration coming soon
+
+
+ }
+ value={state.searchTerm}
+ onChange={(e) => setState(prev => ({ ...prev, searchTerm: e.currentTarget.value }))}
+ style={{ flex: 1 }}
+ />
+ }
+ variant="light"
+ >
+ Load Classes
+
+
+
+ {state.isLoading && (
+
+
+
+ )}
+
+ {!state.isLoading && apexClasses.length === 0 && (
+
+ No Apex classes found. Click "Load Classes" to fetch from your Salesforce org.
-
+ )}
+
+ {!state.isLoading && apexClasses.length > 0 && (
+
+
+ {apexClasses
+ .filter(cls =>
+ cls.name.toLowerCase().includes(state.searchTerm.toLowerCase()) ||
+ cls.body.toLowerCase().includes(state.searchTerm.toLowerCase())
+ )
+ .map((apexClass) => (
+
+
+
+ {apexClass.name}
+
+
+ {apexClass.status}
+
+ {apexClass.isTest && (
+
+ Test Class
+
+ )}
+
+ {apexClass.lengthWithoutComments} chars
+
+
+
+
+ setState(prev => ({
+ ...prev,
+ selectedClass: apexClass,
+ showEditPanel: true
+ }))}
+ >
+
+
+
+
+
+ {new Date(apexClass.lastModifiedDate).toLocaleString()}
+
+
+ ))}
+
+
+ )}
-
-
-
- Salesforce metadata integration coming soon
+
+
+ }
+ value={state.searchTerm}
+ onChange={(e) => setState(prev => ({ ...prev, searchTerm: e.currentTarget.value }))}
+ style={{ flex: 1 }}
+ />
+ }
+ variant="light"
+ >
+ Load Triggers
+
+
+
+ {state.isLoading && (
+
+
+
+ )}
+
+ {!state.isLoading && apexTriggers.length === 0 && (
+
+ No Apex triggers found. Click "Load Triggers" to fetch from your Salesforce org.
+ )}
+
+ {!state.isLoading && apexTriggers.length > 0 && (
+
+
+ {apexTriggers
+ .filter(trigger =>
+ trigger.name.toLowerCase().includes(state.searchTerm.toLowerCase()) ||
+ trigger.tableEnumOrId.toLowerCase().includes(state.searchTerm.toLowerCase()) ||
+ trigger.body.toLowerCase().includes(state.searchTerm.toLowerCase())
+ )
+ .map((trigger) => {
+ const events = [];
+ if (trigger.usageBeforeInsert) events.push('before insert');
+ if (trigger.usageAfterInsert) events.push('after insert');
+ if (trigger.usageBeforeUpdate) events.push('before update');
+ if (trigger.usageAfterUpdate) events.push('after update');
+ if (trigger.usageBeforeDelete) events.push('before delete');
+ if (trigger.usageAfterDelete) events.push('after delete');
+ if (trigger.usageIsAfterUndelete) events.push('after undelete');
+
+ return (
+
+
+
+ {trigger.name}
+
+ SObject: {trigger.tableEnumOrId}
+
+
+
+ {trigger.status}
+
+
+ {events.length} events
+
+
+ {trigger.lengthWithoutComments} chars
+
+
+ {events.length > 0 && (
+
+ Events: {events.join(', ')}
+
+ )}
+
+
+ setState(prev => ({
+ ...prev,
+ selectedTrigger: trigger,
+ showEditPanel: true
+ }))}
+ >
+
+
+
+
+
+ {new Date(trigger.lastModifiedDate).toLocaleString()}
+
+
+ );
+ })}
+
+
+ )}
+
+
+
+
+
+ {/* Left side - Test input */}
+
+
+
+ Test Classes (comma-separated)
+
+
+ }
+ className="query-tab-save-button"
+ disabled={testRunning || testInput.trim().length === 0}
+ >
+ Run Tests
+
+
+
+ {/* Right side - Test results panel */}
+ {state.showTestResultsModal && state.testResults && (
+
+
+
+ Test Execution Results
+
+
setState(prev => ({ ...prev, showTestResultsModal: false }))}
+ >
+
+
+
+
+
+
+
+ {/* Summary */}
+
+
+ : }
+ >
+ {state.testResults.success ? 'Tests Passed' : 'Tests Failed'}
+
+
+
+ {state.testResults.tests_run !== undefined && (
+
+
+ Total Tests: {state.testResults.tests_run}
+
+ {state.testResults.tests_passed !== undefined && (
+
+ Passed: {state.testResults.tests_passed}
+
+ )}
+ {state.testResults.tests_failed !== undefined && (
+
+ Failed: {state.testResults.tests_failed}
+
+ )}
+
+ )}
+
+ {state.testResults.code_coverage !== undefined && (
+
+ Code Coverage: {state.testResults.code_coverage}%
+
+ )}
+
+
+ {/* Test Results */}
+ {state.testResults.test_results && state.testResults.test_results.length > 0 && (
+
+
Test Details
+
+ {state.testResults.test_results.map((test: any, index: number) => (
+
+
+
+ {test.method_name || test.name}
+ {test.class_name && {test.class_name} }
+
+
+ {test.outcome || 'Unknown'}
+
+
+ {test.stack_trace && (
+
+ {test.stack_trace}
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {state.testResults.message && (
+ {state.testResults.message}
+ )}
+
+
+
+
+ )}
+
- {/* Execution Result Modal */}
- setState(prev => ({ ...prev, showExecutionModal: false }))}
- title={tSync('apex.execution.results', 'Apex Execution Results')}
- size="lg"
- >
- {state.executionResult && (
-
-
-
- : }
- >
- {state.executionResult.success ? tSync('apex.success.execution') : tSync('apex.error.execution_failed')}
-
-
-
- {state.executionResult.message && (
- {state.executionResult.message}
- )}
-
- {state.executionResult.compile_problem && (
-
- Compilation Error:
- {state.executionResult.compile_problem}
-
- )}
-
- {state.executionResult.exception_message && (
-
- Runtime Error:
- {state.executionResult.exception_message}
-
- )}
-
-
- {state.executionResult.execution_time && (
-
- Execution Time: {state.executionResult.execution_time}ms
-
- )}
- {state.executionResult.cpu_time && (
-
- CPU Time: {state.executionResult.cpu_time}ms
-
- )}
- {state.executionResult.dml_statements && (
-
- DML Statements: {state.executionResult.dml_statements}
-
- )}
- {state.executionResult.soql_queries && (
-
- SOQL Queries: {state.executionResult.soql_queries}
-
- )}
-
-
- {state.executionResult.debug_info && state.executionResult.debug_info.length > 0 && (
+ {/* Create Modal */}
+ {state.showCreateModal && (
+ {
+ setIsCreateModalClosing(true);
+ setTimeout(() => {
+ setState(prev => ({ ...prev, showCreateModal: false }));
+ setIsCreateModalClosing(false);
+ }, 350);
+ }}
+ >
+
e.stopPropagation()}
+ >
+
+
Create New Apex Code
+ {
+ setIsCreateModalClosing(true);
+ setTimeout(() => {
+ setState(prev => ({ ...prev, showCreateModal: false }));
+ setIsCreateModalClosing(false);
+ }, 350);
+ }}
+ >
+ ร
+
+
+
+
+ {/* Top Section - Basic Info */}
+
+
setFormData({ ...formData, name: e.target.value })}
+ required
+ />
-
Debug Information:
-
-
- {state.executionResult.debug_info.map((log, index) => (
-
- {log}
-
- ))}
-
-
+
Code Type
+
setFormData({ ...formData, code_type: e.target.value as ApexCodeType })}
+ style={{
+ width: '100%',
+ padding: '8px 12px',
+ border: '1px solid #cbd5e1',
+ borderRadius: '4px',
+ fontSize: '14px',
+ backgroundColor: 'white'
+ }}
+ >
+ Anonymous
+ Class
+ Trigger
+ Interface
+ Enum
+ Test Class
+
- )}
-
-
- )}
-
-
- {/* Create Modal */}
- setState(prev => ({ ...prev, showCreateModal: false }))}
- title="Create New Apex Code"
- size="lg"
- >
-
- setFormData({ ...formData, name: e.target.value })}
- required
- />
-
-
-
+
+ {/* Main Body - Editor Left, Settings Right */}
+
+ {/* Left Column - Code Editor */}
+
+
+ Apex Code
+ *
+
+ setFormData({ ...formData, apex_code: value || '' })}
+ options={{
+ minimap: { enabled: false },
+ lineNumbers: 'on',
+ fontSize: 13,
+ fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
+ tabSize: 2,
+ wordWrap: 'on'
+ }}
+ />
+
-
+ {/* Right Column - Settings Sidebar */}
+
+ {/* Description & Tags */}
+
+
Info
+
+ setFormData({ ...formData, description: e.target.value })}
+ size="sm"
+ />
+ setFormData({ ...formData, tags: e.target.value })}
+ size="sm"
+ />
+
+
+
+ {/* Debug Levels */}
+
+
Debug Levels
+
+
+ DB
+ setFormData({ ...formData, debug_levels: { ...formData.debug_levels, DB: e.target.value } })}
+ >
+ NONE
+ ERROR
+ WARN
+ INFO
+ DEBUG
+ FINE
+ FINER
+ FINEST
+
+
+
+ Workflow
+ setFormData({ ...formData, debug_levels: { ...formData.debug_levels, Workflow: e.target.value } })}
+ >
+ NONE
+ ERROR
+ WARN
+ INFO
+ DEBUG
+
+
+
+ Validation
+ setFormData({ ...formData, debug_levels: { ...formData.debug_levels, Validation: e.target.value } })}
+ >
+ NONE
+ ERROR
+ WARN
+ INFO
+ DEBUG
+
+
+
+ Callouts
+ setFormData({ ...formData, debug_levels: { ...formData.debug_levels, Callouts: e.target.value } })}
+ >
+ NONE
+ ERROR
+ WARN
+ INFO
+ DEBUG
+
+
+
+ Apex Code
+ setFormData({ ...formData, debug_levels: { ...formData.debug_levels, Apex_Code: e.target.value } })}
+ >
+ NONE
+ ERROR
+ WARN
+ INFO
+ DEBUG
+ FINE
+ FINER
+ FINEST
+
+
+
+ Apex Profiling
+ setFormData({ ...formData, debug_levels: { ...formData.debug_levels, Apex_Profiling: e.target.value } })}
+ >
+ NONE
+ ERROR
+ WARN
+ INFO
+ DEBUG
+
+
+
+
+
+ {/* Favorite & Options */}
+
+ setFormData({ ...formData, is_favorite: e.target.checked })}
+ />
+
+
+
+
+
+ {/* Footer */}
+
+ {
+ setIsCreateModalClosing(true);
+ setTimeout(() => {
+ setState(prev => ({ ...prev, showCreateModal: false }));
+ setIsCreateModalClosing(false);
+ }, 350);
+ }}
+ >
+ Cancel
+
+
+ Create Apex Code
+
+
+
+
+ )}
+
+ >
);
};
diff --git a/dashboard/src/components/query-editor/QueryTab.tsx b/dashboard/src/components/query-editor/QueryTab.tsx
index 4b0dc6f..bcd7196 100644
--- a/dashboard/src/components/query-editor/QueryTab.tsx
+++ b/dashboard/src/components/query-editor/QueryTab.tsx
@@ -1,17 +1,16 @@
-import React, { useState, useRef, useEffect, useCallback } from 'react';
-import { Button, Paper, Modal, TextInput, Group, ActionIcon, Menu, Tooltip, Text, Badge, Switch } from '@mantine/core';
-import { IconPlayerPlay, IconDatabase, IconDeviceFloppy, IconCode, IconCheck, IconTrash, IconFileImport, IconArrowBack, IconArrowForward, IconBookmark, IconGitBranch, IconChevronDown, IconChevronUp, IconDownload, IconFileExport, IconSettings } from '@tabler/icons-react';
+import { ActionIcon, Badge, Button, Group, Paper, Switch, Text, Tooltip } from '@mantine/core';
import { notifications } from '@mantine/notifications';
import Editor from '@monaco-editor/react';
-import { useTranslation } from '../../services/I18nService';
-import { connectionManager } from '../../services/ConnectionManager';
+import { IconArrowBack, IconArrowForward, IconBookmark, IconCheck, IconChevronDown, IconChevronUp, IconCode, IconDeviceFloppy, IconDownload, IconFileImport, IconGitBranch, IconPlayerPlay, IconTrash } from '@tabler/icons-react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import '../../assets/css/components/query-editor/QueryTab.css';
+import '../../assets/css/components/query-editor/SaveQueryModal.css';
+import { configManager } from '../../config/app.config';
+import { useConnectionSessionStorage } from '../../hooks/useSessionStorage';
import { apiService } from '../../services/ApiService';
+import { useTranslation } from '../../services/I18nService';
import { logger } from '../../services/Logger';
-import { convertASTToGraphData } from '../../utils/astToGraphConverter';
-import { useConnectionSessionStorage } from '../../hooks/useSessionStorage';
import { ResultsViewPage } from '../ResultsViewPage';
-import '../../assets/css/components/query-editor/QueryTab.css';
-import '../../assets/css/components/query-editor/SaveQueryModal.css';
// Saved Query interface
interface SavedQuery {
@@ -27,8 +26,6 @@ interface SavedQuery {
// Import all functionality from the modular structure
import {
- // Query parsing and tree building
- parseQueryStructure,
formatSOQLQuery,
getAutocompleteContext,
parseSoql
@@ -37,32 +34,19 @@ import {
// Legacy treeBuilder, contextDetection, and relationshipResolver imports removed - replaced by enhanced parser
import {
- // Autocomplete
- getSObjectFields,
- getSObjectRelationships,
- getSObjectSuggestions,
createFieldSuggestions,
createRelationshipSuggestions,
- createNestingLevelSuggestion,
filterValidSuggestions,
- createErrorSuggestion
+ // Autocomplete
+ getSObjectFields,
+ getSObjectRelationships,
+ getSObjectSuggestions
} from './QueryTab/autocompleteProvider';
-import {
- // Validation
- SOQLValidator
-} from './QueryTab/queryValidator';
import {
// Drag and drop
- handleDragDrop,
- handleFieldDrop,
- handleRelationshipFieldDrop,
- detectSObjectRelationship,
- detectNestedRelationship,
- getMainFromSObject,
- buildNestedSubquery,
- escapeRegExp
+ handleDragDrop
} from './QueryTab/dragDropHandler';
interface QueryTabProps {
@@ -99,11 +83,11 @@ export const QueryTab: React.FC
= ({
loadedQueryUuid
}) => {
const { tSync } = useTranslation();
-
+
// Results section state management
const [resultsState, setResultsState] = useState<'collapsed' | 'expanded'>('collapsed');
const [resultsHeight, setResultsHeight] = useState(300); // Default height for results section
-
+
// Function to toggle between collapsed and expanded
const toggleResultsState = () => {
setResultsState(prev => prev === 'collapsed' ? 'expanded' : 'collapsed');
@@ -111,7 +95,7 @@ export const QueryTab: React.FC = ({
// Drag separator functionality
const [isDragging, setIsDragging] = useState(false);
-
+
const handleDragStart = (e: React.MouseEvent) => {
e.preventDefault();
setIsDragging(true);
@@ -119,18 +103,18 @@ export const QueryTab: React.FC = ({
const handleDragMove = (e: MouseEvent) => {
if (!isDragging) return;
-
+
// Get the current viewport height and calculate available space
const viewportHeight = window.innerHeight;
const headerHeight = 50; // App header height
const statusBarHeight = 40; // Status bar height
const availableHeight = viewportHeight - headerHeight - statusBarHeight - 20; // Extra padding
-
+
// Calculate new height based on mouse position from bottom
const newHeight = viewportHeight - e.clientY - statusBarHeight;
const minHeight = 100; // Minimum results height
const maxHeight = availableHeight - 150; // Leave minimum space for query editor
-
+
if (newHeight >= minHeight && newHeight <= maxHeight) {
setResultsHeight(newHeight);
}
@@ -145,7 +129,7 @@ export const QueryTab: React.FC = ({
if (isDragging) {
document.addEventListener('mousemove', handleDragMove);
document.addEventListener('mouseup', handleDragEnd);
-
+
return () => {
document.removeEventListener('mousemove', handleDragMove);
document.removeEventListener('mouseup', handleDragEnd);
@@ -160,7 +144,7 @@ export const QueryTab: React.FC = ({
setResultsState('expanded');
}
}, [queryResult, isQuerying]);
-
+
const [query, setQuery] = useState(initialQuery);
const [showSaveModal, setShowSaveModal] = useState(false);
const [saveQueryName, setSaveQueryName] = useState('');
@@ -170,14 +154,16 @@ export const QueryTab: React.FC = ({
const [isLoadingSavedQueries, setIsLoadingSavedQueries] = useState(false);
const [currentSavedQueryUuid, setCurrentSavedQueryUuid] = useState(null);
const [isAutoSaving, setIsAutoSaving] = useState(false);
-
+
// Save modal state
const [saveOperationMode, setSaveOperationMode] = useState<'create' | 'update'>('create');
const [selectedQueryForUpdate, setSelectedQueryForUpdate] = useState(null);
const [isSaveModalClosing, setIsSaveModalClosing] = useState(false);
-
- // Auto-save toggle state
- const [isAutoSaveEnabled, setIsAutoSaveEnabled] = useState(false);
+
+ // Auto-save toggle state - initialize from config
+ const [isAutoSaveEnabled, setIsAutoSaveEnabled] = useState(() => {
+ return configManager.getConfigSection('features').enableAutoSave;
+ });
// Update currentSavedQueryUuid when loadedQueryUuid prop changes
useEffect(() => {
@@ -187,8 +173,9 @@ export const QueryTab: React.FC = ({
// Reset auto-save state when connection changes
useEffect(() => {
setCurrentSavedQueryUuid(null);
- logger.debug('Connection changed, resetting auto-save state', 'QueryTab', {
- newConnectionUuid: currentConnectionUuid
+ setIsAutoSaveEnabled(configManager.getConfigSection('features').enableAutoSave);
+ logger.debug('Connection changed, resetting auto-save state', 'QueryTab', {
+ newConnectionUuid: currentConnectionUuid
});
}, [currentConnectionUuid]);
@@ -196,14 +183,14 @@ export const QueryTab: React.FC = ({
const monacoRef = useRef(null);
const connectionUuidRef = useRef(null);
const providerRef = useRef(null);
-
+
// Session-aware AST storage (shared with SchemaTab) - SINGLE SOURCE OF TRUTH
const [astState, setAstState] = useConnectionSessionStorage(
'ast-state',
currentConnectionUuid,
null
);
-
+
// Removed AST caching - causing issues with duplicates
// Update connection UUID ref whenever it changes
@@ -228,15 +215,15 @@ export const QueryTab: React.FC = ({
const handleEditorDidMount = useCallback((editor: any, monaco: any) => {
editorRef.current = editor;
monacoRef.current = monaco;
-
+
// Configure SOQL language features with enhanced nested query autocomplete
-
+
// CRITICAL: Dispose any existing providers to prevent duplicates
if (providerRef.current) {
providerRef.current.dispose();
providerRef.current = null;
}
-
+
// Register completion provider for SQL language
const provider = monaco.languages.registerCompletionItemProvider('sql', {
provideCompletionItems: async (model: any, position: any) => {
@@ -244,18 +231,18 @@ export const QueryTab: React.FC = ({
// CRITICAL: Always start with a fresh suggestions array
const suggestions = [];
const currentQuery = model.getValue();
-
+
// STEP 1: Parse query directly - parser will tell us if there are errors
const connectionUuid = connectionUuidRef.current;
const autocompleteContext = await getAutocompleteContext(model, position, connectionUuid);
-
+
// If parsing failed, no autocomplete
if (!autocompleteContext) {
return { suggestions: [] };
}
-
+
const { context, ast } = autocompleteContext;
-
+
// Calculate the proper range for text replacement
const word = model.getWordAtPosition(position);
const range = word ? {
@@ -269,7 +256,7 @@ export const QueryTab: React.FC = ({
startColumn: position.column,
endColumn: position.column
};
-
+
if (autocompleteContext) {
// RULE 1: SObject suggestions when cursor is in SObject context (SELECT * FROM)
if (context.nodeType === 'sobject') {
@@ -277,21 +264,21 @@ export const QueryTab: React.FC = ({
const currentQuery = model.getValue();
const cursorOffset = model.getOffsetAt(position);
const partialSObjectName = currentQuery.substring(context.startOffset || cursorOffset, cursorOffset);
-
+
const sobjectSuggestions = await getSObjectSuggestions(partialSObjectName, monaco, position, connectionUuid);
// Ensure SObject suggestions use the calculated range
const sobjectSuggestionsWithRange = sobjectSuggestions.map(s => ({ ...s, range }));
suggestions.push(...sobjectSuggestionsWithRange);
-
+
const validSuggestions = filterValidSuggestions(suggestions);
return { suggestions: validSuggestions };
}
-
+
// RULE 3: Field suggestions when cursor is in field context (using AST data structure)
-
+
// Get the correct SObject for autocomplete
let parentSObjectName = null;
-
+
// For fields within subqueries, use the subquery's SObject
if (context.isInSubquery && context.sObject) {
parentSObjectName = context.sObject;
@@ -301,11 +288,11 @@ export const QueryTab: React.FC = ({
// For regular fields, use the parent SObject
parentSObjectName = ast.sObject;
}
-
+
if (parentSObjectName) {
// Get fields for the parent SObject (metadata is cached per connection per object)
const fields = await getSObjectFields(parentSObjectName, connectionUuid, monaco, position);
-
+
if (fields && Array.isArray(fields)) {
if (fields.length === 1 && fields[0] && fields[0].label && typeof fields[0].label === 'string' && fields[0].label.startsWith('โ ๏ธ')) {
// Ensure error suggestions use the calculated range
@@ -318,7 +305,7 @@ export const QueryTab: React.FC = ({
suggestions.push(...fieldSuggestionsWithRange);
}
}
-
+
// Get relationships for the parent SObject
const relationships = await getSObjectRelationships(parentSObjectName, connectionUuid, monaco, position);
if (relationships && Array.isArray(relationships)) {
@@ -335,20 +322,20 @@ export const QueryTab: React.FC = ({
}
}
}
-
-
+
+
// Final validation: filter out any completion items with invalid properties
const validSuggestions = filterValidSuggestions(suggestions);
-
+
// CRITICAL: Always return a fresh array to prevent Monaco from merging suggestions
// Remove duplicates based on label AND insertText to catch more cases
- const uniqueSuggestions = validSuggestions.filter((suggestion, index, self) =>
- index === self.findIndex(s =>
- s.label === suggestion.label &&
+ const uniqueSuggestions = validSuggestions.filter((suggestion, index, self) =>
+ index === self.findIndex(s =>
+ s.label === suggestion.label &&
s.insertText === suggestion.insertText
)
);
-
+
return { suggestions: uniqueSuggestions };
} catch (error) {
logger.error('Error in autocomplete provider', 'QueryTab', error);
@@ -356,20 +343,20 @@ export const QueryTab: React.FC = ({
}
}
});
-
+
// Store provider reference for cleanup
providerRef.current = provider;
-
+
// Add keyboard shortcut to manually trigger completion
editor.addCommand(monaco.KeyCode.F1, () => {
editor.trigger('manual', 'editor.action.triggerSuggest', {});
});
-
+
// Add Ctrl+Space shortcut to manually trigger completion
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Space, () => {
editor.trigger('manual', 'editor.action.triggerSuggest', {});
});
-
+
// Define custom theme with clean selection colors
monaco.editor.defineTheme('custom-sql-theme', {
base: 'vs',
@@ -386,9 +373,9 @@ export const QueryTab: React.FC = ({
'editor.foreground': '#1e293b'
}
});
-
+
// Monaco Editor setup completed
-
+
// Set editor options - disable Monaco's theme handling
editor.updateOptions({
minimap: { enabled: false },
@@ -526,7 +513,7 @@ export const QueryTab: React.FC = ({
}
}
});
-
+
// Configure undo/redo functionality
editor.addAction({
id: 'undo',
@@ -549,7 +536,7 @@ export const QueryTab: React.FC = ({
ed.getModel()?.redo();
}
});
-
+
}, [fieldData]);
// Cleanup provider on unmount
@@ -575,7 +562,7 @@ export const QueryTab: React.FC = ({
await autoSaveQuery();
}
}
-
+
onExecuteQuery();
};
@@ -606,25 +593,25 @@ export const QueryTab: React.FC = ({
try {
setIsAutoSaving(true);
-
+
await apiService.updateSavedQuery(currentSavedQueryUuid, {
query_text: query.trim(),
updated_by: 'user'
});
-
+
// Update the local saved queries list to reflect the change
- setSavedQueries(prev => prev.map(q =>
- q.saved_queries_uuid === currentSavedQueryUuid
+ setSavedQueries(prev => prev.map(q =>
+ q.saved_queries_uuid === currentSavedQueryUuid
? { ...q, query_text: query.trim(), updated_at: new Date().toISOString() }
: q
));
-
- logger.debug('Query auto-saved successfully', 'QueryTab', {
+
+ logger.debug('Query auto-saved successfully', 'QueryTab', {
queryUuid: currentSavedQueryUuid,
connectionUuid: currentConnectionUuid,
- queryLength: query.trim().length
+ queryLength: query.trim().length
});
-
+
} catch (error) {
logger.error('Failed to auto-save query', 'QueryTab', null, error as Error);
// Don't show error notification for auto-save failures to avoid interrupting user workflow
@@ -649,11 +636,11 @@ export const QueryTab: React.FC = ({
// Parse the query to AST
const ast = await parseSoql(query, currentConnectionUuid);
-
+
// Store the AST in shared session storage for SchemaTab to access
setAstState(ast);
-
-
+
+
if (!ast) {
notifications.show({
title: tSync('query.editor.astToGraph.error.title', 'Invalid Query'),
@@ -707,17 +694,17 @@ export const QueryTab: React.FC = ({
if (!currentConnectionUuid) {
return;
}
-
+
setIsLoadingSavedQueries(true);
-
+
// Add timeout to prevent stuck loading state
const timeoutId = setTimeout(() => {
setIsLoadingSavedQueries(false);
}, 10000); // 10 second timeout
-
+
try {
const response = await apiService.getSavedQueries(currentConnectionUuid);
-
+
// Handle different response structures
let queries = [];
if (Array.isArray(response)) {
@@ -729,7 +716,7 @@ export const QueryTab: React.FC = ({
} else if (response && (response as any).data) {
queries = (response as any).data;
}
-
+
setSavedQueries(queries);
} catch (error) {
logger.error('Failed to load saved queries', 'QueryTab', null, error as Error);
@@ -750,7 +737,7 @@ export const QueryTab: React.FC = ({
setCurrentSavedQueryUuid(null);
return;
}
-
+
const selectedQuery = savedQueries.find(q => q.saved_queries_uuid === queryUuid);
if (selectedQuery) {
handleQueryChange(selectedQuery.query_text);
@@ -770,9 +757,9 @@ export const QueryTab: React.FC = ({
if (editorRef.current) {
try {
// Use our local enhanced parser for professional SOQL formatting
- const currentQuery = editorRef.current.getValue();
+ const currentQuery = editorRef.current.getValue();
const formatted = formatSOQLQuery(currentQuery);
-
+
// Only update if formatting actually changed something
if (formatted !== currentQuery) {
editorRef.current.setValue(formatted);
@@ -810,7 +797,7 @@ export const QueryTab: React.FC = ({
// Show error notification for invalid input
notifications.show({
title: tSync('query.editor.save.error.title', 'Save Failed'),
- message: !currentConnectionUuid
+ message: !currentConnectionUuid
? tSync('query.editor.save.error.no_connection', 'No active connection. Please connect to Salesforce first.')
: tSync('query.editor.save.error.invalid', 'Please provide a valid query name'),
color: 'red',
@@ -832,7 +819,7 @@ export const QueryTab: React.FC = ({
try {
setIsSaving(true);
-
+
if (saveOperationMode === 'create') {
// Create new query
await apiService.createSavedQuery({
@@ -855,24 +842,24 @@ export const QueryTab: React.FC = ({
updated_by: 'user'
});
}
-
+
// Reset form and close modal
setSaveQueryName('');
setShowSaveModal(false);
setSaveOperationMode('create');
setSelectedQueryForUpdate(null);
-
+
// Reload saved queries to reflect changes
await loadSavedQueries();
-
+
// Show success notification
- const title = saveOperationMode === 'create'
+ const title = saveOperationMode === 'create'
? tSync('query.editor.save.success.title', 'Query Saved')
: tSync('query.editor.update.success.title', 'Query Updated');
const message = saveOperationMode === 'create'
? tSync('query.editor.save.success.message', { name: saveQueryName.trim() }) || `"${saveQueryName.trim()}" has been saved successfully`
: tSync('query.editor.update.success.message', { name: saveQueryName.trim() }) || `"${saveQueryName.trim()}" has been updated successfully`;
-
+
notifications.show({
title: title,
message: message,
@@ -880,15 +867,15 @@ export const QueryTab: React.FC = ({
icon: ,
autoClose: 3000,
});
-
+
} catch (error) {
logger.error('Failed to save query', 'QueryTab', null, error as Error);
-
+
// Check for specific error types and show appropriate messages
let errorMessage = saveOperationMode === 'create'
? tSync('query.editor.save.error.message', 'Failed to save the query. Please try again.')
: tSync('query.editor.update.error.message', 'Failed to update the query. Please try again.');
-
+
if (error instanceof Error) {
if (error.message.includes('saved_query.error.duplicate_name')) {
errorMessage = tSync('saved_query.error.duplicate_name', 'Query with this name already exists');
@@ -900,10 +887,10 @@ export const QueryTab: React.FC = ({
errorMessage = tSync('saved_query.error.invalid_connection', 'Invalid or missing connection');
}
}
-
+
// Show error notification
notifications.show({
- title: saveOperationMode === 'create'
+ title: saveOperationMode === 'create'
? tSync('query.editor.save.error.title', 'Save Failed')
: tSync('query.editor.update.error.title', 'Update Failed'),
message: errorMessage,
@@ -917,8 +904,8 @@ export const QueryTab: React.FC = ({
const openSaveModal = () => {
// Set default name based on query content
- const defaultName = query.length > 30
- ? `${query.substring(0, 30)}...`
+ const defaultName = query.length > 30
+ ? `${query.substring(0, 30)}...`
: query || tSync('query.editor.untitled', 'Untitled Query');
setSaveQueryName(defaultName);
setSaveOperationMode('create');
@@ -937,16 +924,16 @@ export const QueryTab: React.FC = ({
};
return (
-
-
= ({
color="blue"
onClick={handleUndo}
leftSection={ }
- style={{
- padding: '4px 8px',
+ style={{
+ padding: '4px 8px',
minHeight: '24px',
fontSize: '11px',
fontWeight: 500,
@@ -1002,15 +989,15 @@ export const QueryTab: React.FC = ({
>
{tSync('query.editor.undo', 'Undo')}
-
+
}
- style={{
- padding: '4px 8px',
+ style={{
+ padding: '4px 8px',
minHeight: '24px',
fontSize: '11px',
fontWeight: 500,
@@ -1022,7 +1009,7 @@ export const QueryTab: React.FC = ({
{tSync('query.editor.redo', 'Redo')}
-
+
{/* Format/Clear Group */}
= ({
onClick={handleFormatQuery}
disabled={!query.trim()}
leftSection={ }
- style={{
- padding: '4px 8px',
+ style={{
+ padding: '4px 8px',
minHeight: '24px',
fontSize: '11px',
fontWeight: 500,
@@ -1044,7 +1031,7 @@ export const QueryTab: React.FC = ({
>
{tSync('query.editor.format', 'Format')}
-
+
= ({
onClick={handleClearQuery}
disabled={!query.trim()}
leftSection={ }
- style={{
- padding: '4px 8px',
+ style={{
+ padding: '4px 8px',
minHeight: '24px',
fontSize: '11px',
fontWeight: 500,
@@ -1069,50 +1056,57 @@ export const QueryTab: React.FC = ({
{/* Right Side - Execution and Management Actions */}
- {/* Auto-save Toggle */}
- {currentConnectionUuid && currentSavedQueryUuid && (
-
0 && (
+
-
-
setIsAutoSaveEnabled(event.currentTarget.checked)}
+ disabled={!currentSavedQueryUuid}
styles={{
track: {
- backgroundColor: isAutoSaveEnabled ? '#16a34a' : '#d1d5db',
- border: 'none'
+ backgroundColor: (isAutoSaveEnabled && currentSavedQueryUuid) ? '#16a34a' : '#d1d5db',
+ border: 'none',
+ opacity: currentSavedQueryUuid ? 1 : 0.5
}
}}
/>
-
{tSync('query.editor.autoSave.toggle.label', 'Auto-save')}
@@ -1124,17 +1118,17 @@ export const QueryTab: React.FC = ({
{/* Quick Load Saved Queries */}
{currentConnectionUuid && (
-
handleQuickLoadQuery(e.target.value || null)}
@@ -1153,7 +1147,7 @@ export const QueryTab: React.FC = ({
}}
>
- {isLoadingSavedQueries
+ {isLoadingSavedQueries
? tSync('query.editor.quickLoad.loading', 'Loading...')
: savedQueries.length === 0
? tSync('query.editor.quickLoad.noQueries', 'No Saved Queries')
@@ -1161,8 +1155,8 @@ export const QueryTab: React.FC = ({
}
{savedQueries.map(query => (
-
{query.is_favorite ? 'โ
' : ''}{query.name}
@@ -1177,9 +1171,9 @@ export const QueryTab: React.FC = ({
{/* Auto-save indicator */}
{isAutoSaving && (
- = ({
)}
-
+
= ({
disabled={!query.trim() || isQuerying || isAutoSaving}
loading={isQuerying || isAutoSaving}
leftSection={ }
- style={{
- padding: '4px 8px',
+ style={{
+ padding: '4px 8px',
minHeight: '24px',
fontSize: '11px',
fontWeight: 500,
@@ -1213,7 +1207,7 @@ export const QueryTab: React.FC = ({
>
{tSync('query.editor.execute', 'Run')}
-
+
= ({
onClick={convertASTToGraph}
disabled={!query.trim()}
leftSection={ }
- style={{
- padding: '4px 8px',
+ style={{
+ padding: '4px 8px',
minHeight: '24px',
fontSize: '11px',
fontWeight: 500,
@@ -1244,8 +1238,8 @@ export const QueryTab: React.FC = ({
onClick={openSaveModal}
disabled={!query.trim() || !currentConnectionUuid}
leftSection={ }
- style={{
- padding: '4px 8px',
+ style={{
+ padding: '4px 8px',
minHeight: '24px',
fontSize: '11px',
fontWeight: 500,
@@ -1256,7 +1250,7 @@ export const QueryTab: React.FC = ({
>
{tSync('query.editor.saveQuery', 'Save')}
-
+
= ({
onClick={() => document.getElementById('query-file-input')?.click()}
disabled={false}
leftSection={ }
- style={{
- padding: '4px 8px',
+ style={{
+ padding: '4px 8px',
minHeight: '24px',
fontSize: '11px',
fontWeight: 500,
@@ -1279,7 +1273,7 @@ export const QueryTab: React.FC = ({
-
+
{/* Hidden file input for importing queries */}
= ({
const content = event.target?.result as string;
if (content) {
handleQueryChange(content);
-
+
// Show success notification
notifications.show({
title: tSync('query.editor.import.success.title', 'Query Imported'),
@@ -1303,7 +1297,7 @@ export const QueryTab: React.FC = ({
icon: ,
autoClose: 3000,
});
-
+
// Reset file input
e.target.value = '';
}
@@ -1322,11 +1316,11 @@ export const QueryTab: React.FC = ({
}}
/>
-
- = ({
{/* Save Query Modal */}
{showSaveModal && (
-
{
setIsSaveModalClosing(true);
@@ -1375,18 +1369,18 @@ export const QueryTab: React.FC
= ({
}, 350);
}}
>
- e.stopPropagation()}
>
- {saveOperationMode === 'create'
+ {saveOperationMode === 'create'
? tSync('query.editor.saveModal.title', 'Save Query')
: tSync('query.editor.updateModal.title', 'Update Query')
}
- {
setIsSaveModalClosing(true);
@@ -1402,7 +1396,7 @@ export const QueryTab: React.FC = ({
ร
-
+
{/* Operation Mode Selection */}
@@ -1419,8 +1413,8 @@ export const QueryTab: React.FC
= ({
if (e.target.value === 'create') {
setSelectedQueryForUpdate(null);
// Reset to default name for create mode
- const defaultName = query.length > 30
- ? `${query.substring(0, 30)}...`
+ const defaultName = query.length > 30
+ ? `${query.substring(0, 30)}...`
: query || tSync('query.editor.untitled', 'Untitled Query');
setSaveQueryName(defaultName);
}
@@ -1439,8 +1433,8 @@ export const QueryTab: React.FC = ({
if (e.target.value === 'create') {
setSelectedQueryForUpdate(null);
// Reset to default name for create mode
- const defaultName = query.length > 30
- ? `${query.substring(0, 30)}...`
+ const defaultName = query.length > 30
+ ? `${query.substring(0, 30)}...`
: query || tSync('query.editor.untitled', 'Untitled Query');
setSaveQueryName(defaultName);
}
@@ -1479,7 +1473,7 @@ export const QueryTab: React.FC = ({
{/* Query Name Input */}
- {saveOperationMode === 'create'
+ {saveOperationMode === 'create'
? tSync('query.editor.saveModal.name.label', 'Query Name')
: tSync('query.editor.updateModal.name.label', 'New Query Name')
}
@@ -1487,7 +1481,7 @@ export const QueryTab: React.FC = ({
= ({
disabled={saveOperationMode === 'update' && !selectedQueryForUpdate}
/>
- {saveOperationMode === 'create'
+ {saveOperationMode === 'create'
? tSync('query.editor.saveModal.name.description', 'Choose a descriptive name for your query')
: tSync('query.editor.updateModal.name.description', 'Enter the new name for the selected query')
}
-
+
-
{
setIsSaveModalClosing(true);
@@ -1520,18 +1514,18 @@ export const QueryTab: React.FC = ({
>
{tSync('common.cancel', 'Cancel')}
-
- {isSaving
+ {isSaving
? (saveOperationMode === 'create' ? 'Saving...' : 'Updating...')
- : (saveOperationMode === 'create'
+ : (saveOperationMode === 'create'
? tSync('query.editor.saveModal.save', 'Save Query')
: tSync('query.editor.updateModal.update', 'Update Query')
)
@@ -1545,7 +1539,7 @@ export const QueryTab: React.FC = ({
{/* Draggable Separator */}
{queryResult && resultsState === 'expanded' && (
- = ({
{/* Query Results Section - Only show when there are results */}
{queryResult && (
-
= ({
}}
>
{resultsState === 'collapsed' ? (
-
= ({
backgroundColor: '#f8fafc'
}}>
- = ({
) : (
<>
-
= ({
backgroundColor: '#f8fafc'
}}>
- = ({
)}
{maxRecordsWarning && (
- = ({
)}
{maxRecordsReached && (
- = ({
{/* Export Button - Exact copy from original App.tsx */}
{queryResult && queryResult.records && queryResult.records.length > 0 && (
- {
// Export functionality - exact copy from original
const event = new CustomEvent('exportData', { detail: { format: 'json' } });
window.dispatchEvent(event);
}}
leftSection={ }
- style={{
- padding: '2px 6px',
+ style={{
+ padding: '2px 6px',
minHeight: '20px',
fontSize: '10px',
fontWeight: 500,
@@ -1710,18 +1704,18 @@ export const QueryTab: React.FC = ({
Export
)}
-
+
{/* Query More Button - Always show when there are results */}
{queryResult && queryResult.records && queryResult.records.length > 0 && (
- = maxRecordsLimit || !queryResult.metadata?.nextRecordsUrl}
leftSection={ }
- style={{
- padding: '2px 6px',
+ style={{
+ padding: '2px 6px',
minHeight: '20px',
fontSize: '10px',
fontWeight: 500,
@@ -1730,17 +1724,17 @@ export const QueryTab: React.FC = ({
border: `1px solid ${(isQuerying || (queryResult.records?.length || 0) >= maxRecordsLimit || !queryResult.metadata?.nextRecordsUrl) ? '#94a3b8' : '#3b82f6'}`
}}
>
- {isQuerying ? tSync('common.loading', 'Loading...') :
+ {isQuerying ? tSync('common.loading', 'Loading...') :
(queryResult.records?.length || 0) >= maxRecordsLimit ? tSync('results.maxReached', 'Max Reached') :
!queryResult.metadata?.nextRecordsUrl ? tSync('results.noMoreRecords', 'No More Records') : tSync('results.loadMore', 'Load More')}
)}
-
+
-
{
return (import.meta as any).env[key] || defaultValue;
};
+// Resolves the backend API host at runtime (in the browser) rather than baking it in
+// at build time. `localhost` baked into a built bundle resolves to the *viewer's* machine,
+// not the server that served the page - so this defaults to whatever host served this page,
+// on the documented backend port. VITE_API_BASE_URL remains available as an explicit
+// override for split-host setups (e.g. frontend and backend on different domains).
+const getDefaultApiBaseUrl = (): string => {
+ if (typeof window !== 'undefined' && window.location?.hostname) {
+ return `${window.location.protocol}//${window.location.hostname}:8001`;
+ }
+ return 'http://localhost:8001';
+};
+
const getEnvBoolean = (key: string, defaultValue: boolean): boolean => {
const value = (import.meta as any).env[key];
if (value === undefined) return defaultValue;
@@ -209,8 +222,8 @@ export const defaultAppConfig: AppConfig = {
},
api: {
- baseUrl: getEnvVar('VITE_API_BASE_URL', 'http://localhost:8001'),
- timeout: getEnvNumber('VITE_API_TIMEOUT', 30000),
+ baseUrl: getEnvVar('VITE_API_BASE_URL', getDefaultApiBaseUrl()),
+ timeout: getEnvNumber('VITE_API_TIMEOUT', 120000),
retries: getEnvNumber('VITE_API_RETRIES', 3),
endpoints: {
health: '/api/v1/health',
@@ -223,6 +236,7 @@ export const defaultAppConfig: AppConfig = {
authProviders: '/api/v1/auth-providers',
appSettings: '/api/v1/settings',
savedQueries: '/api/v1/saved-queries',
+ savedApex: '/api/v1/saved-apex',
aiAgents: '/api/v1/ai-agents',
datapilotAgent: 'datapilot-agent'
}
diff --git a/dashboard/src/i18n/en.json b/dashboard/src/i18n/en.json
new file mode 100644
index 0000000..587b533
--- /dev/null
+++ b/dashboard/src/i18n/en.json
@@ -0,0 +1,1176 @@
+{
+ "aiAssistant.tab.title": "AI Assistant",
+ "aiAssistant.tab.subtitle": "Ask questions, generate queries, and get insights about your Salesforce data with DataPilot Agent",
+ "aiAssistant.input.title": "Ask DataPilot Agent",
+ "aiAssistant.input.description": "Ask questions about Salesforce data, generate SOQL queries, or get insights with AI assistance",
+ "aiAssistant.input.placeholder": "Ask DataPilot Agent anything about your Salesforce data, like 'Show me all Account fields' or 'Generate a query for recent opportunities'",
+ "aiAssistant.input.generating": "Processing...",
+ "aiAssistant.input.generate": "Ask DataPilot Agent",
+ "aiAssistant.conversations.title": "Recent Conversations",
+ "aiAssistant.conversations.empty.title": "No conversations yet",
+ "aiAssistant.conversations.empty.description": "Start chatting with DataPilot Agent to see your conversations here",
+ "aiAssistant.recentConversations.title": "Recent Conversations",
+ "aiAssistant.recentConversations.empty": "No recent conversations yet. Start chatting with DataPilot Agent!",
+ "aiAssistant.recentConversations.conversation": "Conversation",
+ "aiAssistant.recentConversations.reuse": "Reuse Message",
+ "aiAssistant.conversation.request": "Your Question",
+ "aiAssistant.conversation.response": "Agent Response",
+ "aiAssistant.conversation.useQuery": "Use this query in the editor",
+ "aiAssistant.suggestions.title": "Query Suggestions",
+ "aiAssistant.suggestion.category.basic": "Basic",
+ "aiAssistant.suggestion.category.advanced": "Advanced",
+ "aiAssistant.suggestion.category.analytics": "Analytics",
+ "aiAssistant.suggestion.category.custom": "Custom",
+ "aiAssistant.suggestion.difficulty.easy": "Easy",
+ "aiAssistant.suggestion.difficulty.medium": "Medium",
+ "aiAssistant.suggestion.difficulty.hard": "Hard",
+ "aiAssistant.suggestion.tags": "Tags: {tags}",
+ "aiAssistant.suggestion.useQuery": "Use this query",
+ "aiAssistant.status.connected": "DataPilot Agent Online",
+ "aiAssistant.status.offline": "DataPilot Agent Offline",
+ "aiAssistant.status.websocket.connected": "WebSocket Connected",
+ "aiAssistant.status.websocket.connecting": "WebSocket Connecting...",
+ "aiAssistant.status.websocket.chat": "Real-time chat with DataPilot Agent (WebSocket)",
+ "aiAssistant.status.websocket.connecting.chat": "Connecting to real-time chat...",
+ "aiAssistant.status.unavailable": "DataPilot Agent is not available. Please check your connection.",
+ "aiAssistant.status.error.title": "DataPilot Agent Error",
+ "aiAssistant.status.sending": "Sending...",
+ "aiAssistant.realtime.label": "Real-time Chat:",
+ "aiAssistant.notifications.noConversations.title": "No Conversations Available",
+ "aiAssistant.notifications.noConversations.message": "Please create a new conversation first before chatting with the AI Agent.",
+ "aiAssistant.notifications.noConversationSelected.title": "No Conversation Selected",
+ "aiAssistant.notifications.noConversationSelected.message": "Please select a conversation from the list on the right before asking a question.",
+ "aiAssistant.metadata.intent": "Intent:",
+ "aiAssistant.metadata.understood": "Understood:",
+ "aiAssistant.metadata.confidence": "Confidence:",
+ "aiAssistant.metadata.execution_time": "ms",
+ "aiAssistant.metadata.tools_used": "Tools used:",
+ "aiAssistant.metadata.actions": "Actions:",
+ "conversations.title": "Recent Conversations",
+ "conversations.search.placeholder": "Search conversations...",
+ "conversations.empty.title": "No conversations yet",
+ "conversations.empty.subtitle": "Start chatting with DataPilot Agent to see your conversations here",
+ "conversations.empty.search": "No conversations found",
+ "conversations.empty.searchSubtitle": "Try adjusting your search terms",
+ "conversations.actions.refresh": "Refresh",
+ "conversations.actions.create": "New",
+ "conversations.actions.createFirst": "Create First Conversation",
+ "conversations.actions.reuse": "Reuse Message",
+ "conversations.loading": "Loading conversations...",
+ "conversations.showing": "Showing {count} of {total}",
+ "conversations.create.title": "Create New Conversation",
+ "conversations.create.titleLabel": "Conversation Title",
+ "conversations.create.titlePlaceholder": "Enter a title for your conversation (optional)",
+ "conversations.create.descriptionLabel": "Description (Optional)",
+ "conversations.create.descriptionPlaceholder": "Enter a description for your conversation",
+ "conversations.create.tagsLabel": "Tags (Optional)",
+ "conversations.create.tagsPlaceholder": "Enter tags separated by commas (e.g., salesforce, soql, metadata)",
+ "conversations.create.initialMessage": "Initial Message",
+ "conversations.create.create": "Create Conversation",
+ "conversations.edit.title": "Edit Conversation",
+ "conversations.edit.update": "Update Conversation",
+ "aiAssistant.recentConversations.noConnection": "Connect to Salesforce to manage conversations",
+ "aiAssistant.smartResponse.intentUnderstanding": "Intent Understanding",
+ "aiAssistant.smartResponse.intentNotAvailable": "Intent analysis not available",
+ "aiAssistant.smartResponse.actionsTaken": "Actions taken:",
+ "aiAssistant.smartResponse.confidence.high": "High",
+ "aiAssistant.smartResponse.confidence.medium": "Medium",
+ "aiAssistant.smartResponse.confidence.low": "Low",
+ "aiAssistant.smartResponse.confidence.unknown": "Unknown",
+ "aiAssistant.smartResponse.confidence.label": "Confidence",
+ "aiAssistant.smartResponse.fields": "Fields",
+ "aiAssistant.smartResponse.showMore": "Show More",
+ "aiAssistant.smartResponse.remaining": "remaining",
+ "aiAssistant.smartResponse.fieldName": "Field Name",
+ "aiAssistant.smartResponse.label": "Label",
+ "aiAssistant.smartResponse.type": "Type",
+ "aiAssistant.smartResponse.required": "Required",
+ "aiAssistant.smartResponse.description": "Description",
+ "aiAssistant.smartResponse.noDescription": "No description",
+ "aiAssistant.smartResponse.foundObjects": "Found {count} related objects. Select one to view details.",
+ "aiAssistant.smartResponse.sampleFields": "Sample Fields:",
+ "aiAssistant.smartResponse.moreFields": "+{count} more fields",
+ "aiAssistant.smartResponse.queryResults": "Query Results",
+ "aiAssistant.smartResponse.records": "records",
+ "aiAssistant.smartResponse.generatedSoql": "Generated SOQL Query:",
+ "aiAssistant.smartResponse.sampleRecords": "Sample Records",
+ "aiAssistant.streamingChat": "Streaming Chat",
+ "aiAssistant.smartResponse.loadMore": "Load More",
+ "aiAssistant.smartResponse.clarificationTitle": "Clarification Needed",
+ "aiAssistant.smartResponse.intentUnderstood": "Intent understood:",
+ "aiAssistant.smartResponse.clarificationQuestion": "Clarification question:",
+ "aiAssistant.smartResponse.pleaseChooseOptions": "Please choose one of the following options:",
+ "aiAssistant.smartResponse.clarificationReason": "Please select which object you want to explore.",
+ "aiAssistant.smartResponse.fieldsAvailable": "fields available",
+ "aiAssistant.smartResponse.explore": "Explore",
+ "aiAssistant.smartResponse.objectRelationships": "Object Relationships",
+ "aiAssistant.smartResponse.parentRelationships": "Parent Relationships",
+ "aiAssistant.smartResponse.childRelationships": "Child Relationships",
+ "aiAssistant.smartResponse.lookupRelationships": "Lookup Relationships",
+ "aiAssistant.smartResponse.relationshipName": "Relationship Name",
+ "aiAssistant.smartResponse.childObject": "Child Object",
+ "aiAssistant.smartResponse.referenceTo": "Reference To",
+ "aiAssistant.smartResponse.fieldDetails": "Field Details",
+ "aiAssistant.smartResponse.basicInformation": "Basic Information",
+ "aiAssistant.smartResponse.fieldProperties": "Field Properties",
+ "aiAssistant.smartResponse.length": "Length",
+ "aiAssistant.smartResponse.precision": "Precision",
+ "aiAssistant.smartResponse.scale": "Scale",
+ "aiAssistant.smartResponse.unique": "Unique",
+ "aiAssistant.smartResponse.calculated": "Calculated",
+ "aiAssistant.smartResponse.createable": "Createable",
+ "aiAssistant.smartResponse.updateable": "Updateable",
+ "aiAssistant.smartResponse.formula": "Formula",
+ "aiAssistant.smartResponse.picklistValues": "Picklist Values",
+ "aiAssistant.smartResponse.value": "Value",
+ "aiAssistant.smartResponse.active": "Active",
+ "aiAssistant.smartResponse.default": "Default",
+ "aiAssistant.smartResponse.viewAllValues": "View All Values",
+ "aiAssistant.smartResponse.andMoreValues": "... and {count} more values",
+ "aiAssistant.smartResponse.picklistValuesDescription": "All available picklist values for this field",
+ "aiAssistant.smartResponse.metadataInformation": "Metadata Information",
+ "aiAssistant.smartResponse.dataResults": "Data Results",
+ "aiAssistant.smartResponse.suggestions": "Suggestions",
+ "aiAssistant.smartResponse.noDataToDisplay": "No data available to display",
+ "aiAssistant.smartResponse.unknownResponseType": "Unknown response type: {type}",
+ "aiAssistant.smartResponse.noRelationshipFound": "No {relationshipName} found",
+ "aiAssistant.smartResponse.detectedObject": "Detected object:",
+ "aiAssistant.smartResponse.noRelationshipsFound": "No relationships found for this object.",
+ "aiAssistant.smartResponse.closeModal": "Close",
+ "conversations.notifications.created.title": "Conversation Created",
+ "conversations.notifications.created.message": "New conversation \"{title}\" has been created successfully",
+ "conversations.notifications.updated.title": "Conversation Updated",
+ "conversations.notifications.updated.message": "Conversation \"{title}\" has been updated successfully",
+ "conversations.notifications.deleted.title": "Conversation Deleted",
+ "conversations.notifications.deleted.message": "Conversation \"{title}\" has been deleted successfully",
+ "conversations.notifications.error.create.title": "Create Conversation Failed",
+ "conversations.notifications.error.create.message": "Failed to create conversation. Please try again",
+ "conversations.notifications.error.update.title": "Update Conversation Failed",
+ "conversations.notifications.error.update.message": "Failed to update conversation. Please try again",
+ "conversations.notifications.error.delete.title": "Delete Conversation Failed",
+ "conversations.notifications.error.delete.message": "Failed to delete conversation. Please try again",
+ "conversations.messages.summary_updated": "Conversation summary updated successfully",
+ "aiAssistant.thinking": "AI Agent is thinking...",
+ "aiAssistant.thinkingProcess": "AI Thinking Process:",
+ "aiAssistant.response": "Response:",
+ "aiAssistant.liveResponse": "Live Response:",
+ "aiAssistant.finalResponse": "Final Response:",
+ "aiAssistant.processingRequest": "Processing your request...",
+ "aiAssistant.thinkingLabel": "AI Thinking:",
+ "aiAssistant.errors.timeout": "Operation timed out. Please try again.",
+ "aiAssistant.errors.noConversationSelected": "Please select a conversation before asking a question",
+ "aiAssistant.errors.processFailed": "Failed to process query",
+ "aiAssistant.errors.loadMessages.title": "Error Loading Messages",
+ "aiAssistant.errors.loadMessages.message": "Failed to load conversation messages. Please try again.",
+ "aiAssistant.processingSteps.connecting": "Connecting to AI Agent...",
+ "aiAssistant.processingSteps.sending": "Sending message to AI Agent...",
+ "aiAssistant.processingSteps.waiting": "Waiting for AI response...",
+ "aiAssistant.processingSteps.analyzing": "Analyzing your request...",
+ "aiAssistant.processingSteps.processing": "Processing response...",
+ "aiAssistant.processingSteps.finalizing": "Finalizing response...",
+ "aiAssistant.thinkingOverlay.processingRequest": "Processing your request...",
+ "aiAssistant.thinkingOverlay.thinking": "Thinking",
+ "aiAssistant.thinkingOverlay.progress": "Progress",
+ "aiAssistant.thinkingOverlay.currentStep": "Current Step",
+ "aiAssistant.thinkingOverlay.steps.analyzingRequest": "Analyzing Request",
+ "aiAssistant.thinkingOverlay.steps.selectingTools": "Selecting Tools",
+ "aiAssistant.thinkingOverlay.steps.queryingData": "Querying Data",
+ "aiAssistant.thinkingOverlay.steps.processingLogic": "Processing Logic",
+ "aiAssistant.thinkingOverlay.steps.generatingResponse": "Generating Response",
+ "aiAssistant.thinkingOverlay.toolSelection.waiting": "Analyzing your query...",
+ "aiAssistant.thinkingOverlay.toolSelection.analyzing": "Evaluating available tools...",
+ "aiAssistant.thinkingOverlay.toolSelection.selecting": "Selecting best approach...",
+ "aiAssistant.thinkingOverlay.toolSelection.deciding": "Deciding on tools to use...",
+ "aiAssistant.thinkingOverlay.toolSelection.preparing": "Preparing tool execution...",
+ "aiAssistant.thinkingOverlay.tools.searchObjects": "Search Objects",
+ "aiAssistant.thinkingOverlay.tools.getObjectDetails": "Get Object Details",
+ "aiAssistant.thinkingOverlay.tools.findRelationships": "Find Relationships",
+ "aiAssistant.thinkingOverlay.tools.queryData": "Query Data",
+ "aiAssistant.chat.you": "You",
+ "aiAssistant.chat.ai": "AI Assistant",
+ "common.time.created": "Created at",
+ "common.time.updated": "Updated",
+ "apex.title": "Apex Code Manager",
+ "apex.subtitle": "View and manage Apex classes and triggers",
+ "apex.coming_soon.title": "Coming in Next Release",
+ "apex.coming_soon.message": "The Apex functionality is currently under development and will be available in the next release. This will include Apex code execution, saved Apex management, and Salesforce metadata integration.",
+ "apex.search.placeholder": "Search Apex code...",
+ "apex.filter.status.placeholder": "Filter by status",
+ "apex.filter.status.all": "All Status",
+ "apex.filter.status.active": "Active",
+ "apex.filter.status.inactive": "Inactive",
+ "apex.actions.deploy": "Deploy",
+ "apex.actions.export": "Export",
+ "apex.tabs.classes": "Classes ({count})",
+ "apex.tabs.triggers": "Triggers ({count})",
+ "apex.class.modified": "Modified: {date}",
+ "apex.class.test": "Test",
+ "apex.class.testClass": "Test Class",
+ "apex.class.meta": "Last Modified: {modified} | Created by: {createdBy}",
+ "apex.class.code": "Code",
+ "apex.trigger.object": "Object: {object}",
+ "apex.trigger.meta": "Last Modified: {modified} | Created by: {createdBy}",
+ "apex.trigger.code": "Code",
+ "apex.editCode": "Edit Apex Code",
+ "apex.codeType": "Code Type",
+ "apex.anonymous": "Anonymous",
+ "apex.class": "Class",
+ "apex.trigger": "Trigger",
+ "apex.interface": "Interface",
+ "apex.enum": "Enum",
+ "apex.testClass": "Test Class",
+ "apex.code": "Apex Code",
+ "apex.createNew": "Create New Apex Code",
+ "apex.codePlaceholder": "Enter your Apex code here",
+ "apex.markFavorite": "Mark as Favorite",
+ "apex.status.active": "Active",
+ "apex.status.inactive": "Inactive",
+ "apex.error.no_connection": "No active Salesforce connection. Please connect to Salesforce first.",
+ "apex.error.validation": "Apex validation error: {error}",
+ "apex.error.internal": "Internal server error during Apex operation: {error}",
+ "apex.error.execution_failed": "Apex code execution failed",
+ "apex.success.execution": "Apex code executed successfully",
+ "apex.error.compilation_failed": "Apex compilation failed",
+ "apex.error.test_execution_failed": "Apex test execution failed",
+ "apex.error.limits_retrieval_failed": "Failed to retrieve Apex limits",
+ "apex.error.status_retrieval_failed": "Failed to retrieve compilation status",
+ "apex.messages.execution_completed": "Apex execution completed successfully",
+ "apex.messages.execution_failed": "Apex execution failed",
+ "apex.filter.codeType.interface": "Interface",
+ "apex.filter.codeType.enum": "Enum",
+ "apex.filter.codeType.test_class": "Test Class",
+ "apex.form.name": "Name",
+ "apex.form.namePlaceholder": "Enter Apex code name",
+ "apex.form.description": "Description",
+ "apex.form.descriptionPlaceholder": "Optional description",
+ "apex.form.tags": "Tags",
+ "apex.form.tagsPlaceholder": "Comma-separated tags",
+ "apex.form.favorite": "Favorite",
+ "apex.form.codePlaceholder": "Enter your Apex code here",
+ "apex.execution.results": "Apex Execution Results",
+ "app.title": "DataPilot",
+ "app.name": "DataPilot",
+ "app.subtitle": "Your AI pilot for Salesforce data navigation",
+ "app.description": "Advanced SOQL query editor with AI-powered intelligence, schema exploration, result analysis, and smart data navigation",
+ "app.feature.secure": "Secure",
+ "app.feature.fast": "Fast",
+ "app.feature.professional": "Professional",
+ "app.feature.smart": "Smart",
+ "app.feature.modern": "Modern",
+ "app.initializing": "Initializing...",
+ "app.version": "v1.0.0",
+ "app.unknownInitializationError": "Unknown initialization error",
+ "app.failedToReloadSchema": "Failed to reload schema",
+ "app.initializationFailed": "Initialization Failed",
+ "app.checkingAuth": "Checking authentication status...",
+ "app.title.fallback": "DataPilot",
+ "app.subtitle.fallback": "Your AI pilot for Salesforce data navigation",
+ "app.description.fallback": "Connect to your Salesforce organization and navigate data with AI-powered SOQL queries",
+ "app.expandSchemaTree": "Expand SObjects Tree",
+ "app.collapseSchemaTree": "Collapse SObjects Tree",
+ "app.expandResultsMiddle": "Expand Results (Middle)",
+ "app.collapseResultsMiddle": "Collapse Results (Middle)",
+ "app.expandResultsFull": "Expand Results (Full)",
+ "app.sobjectsTree": "SObjects Tree",
+ "app.backendServerRequired": "Backend Server Required",
+ "auth_providers.errors.not_found": "Auth provider not found",
+ "auth_providers.errors.invalid_data": "Invalid auth provider data",
+ "auth_providers.errors.invalid_update_data": "Invalid auth provider update data",
+ "auth_providers.errors.invalid_provider_type": "Invalid provider type",
+ "auth_providers.errors.must_be_one_of": "Must be one of",
+ "auth_providers.notifications.authError.title": "Authentication Error",
+ "auth_providers.notifications.authError.message": "Authentication failed. Please check your credentials and try again.",
+ "common.close": "Close",
+ "common.settings": "Settings",
+ "common.general": "General",
+ "common.appearance": "Appearance",
+ "common.language": "Language",
+ "common.advanced": "Advanced",
+ "common.retry": "Retry",
+ "common.cancel": "Cancel",
+ "common.save": "Save",
+ "common.loading": "Loading...",
+ "common.yes": "Yes",
+ "common.no": "No",
+ "common.resetToDefaults": "Reset to Defaults",
+ "common.short": "Short",
+ "common.long": "Long",
+ "common.dateFormat": "Date Format",
+ "common.note": "Note",
+ "common.backToApp": "Back to App",
+ "common.notConnected": "Not Connected",
+ "common.configure": "Configure",
+ "common.status.success": "Success",
+ "common.status.error": "Error",
+ "common.status.running": "Running",
+ "common.status.connected": "Connected",
+ "common.status.disconnected": "Disconnected",
+ "common.status.connecting": "Connecting...",
+ "common.status.querying": "Querying...",
+ "common.filter.all": "All",
+ "common.filter.standard": "Standard",
+ "common.filter.custom": "Custom",
+ "common.actions.export": "Export",
+ "common.actions.refresh": "Refresh",
+ "common.actions.clear": "Clear",
+ "common.actions.delete": "Delete",
+ "common.actions.edit": "Edit",
+ "common.actions.copy": "Copy",
+ "common.actions.execute": "Execute",
+ "common.actions.load": "Load",
+ "common.actions.save": "Save",
+ "common.actions.cancel": "Cancel",
+ "common.actions.retry": "Retry",
+ "common.errors.error": "Error",
+ "common.time.justNow": "Just now",
+ "common.time.hoursAgo": "{hours}h ago",
+ "common.time.daysAgo": "{days}d ago",
+ "common.placeholder.search": "Search...",
+ "common.placeholder.filter": "Filter...",
+ "common.labels.title": "Title",
+ "common.labels.subtitle": "Subtitle",
+ "common.labels.name": "Name",
+ "common.labels.type": "Type",
+ "common.labels.description": "Description",
+ "common.labels.timestamp": "Timestamp",
+ "common.labels.message": "Message",
+ "common.labels.details": "Details",
+ "common.labels.actions": "Actions",
+ "common.labels.status": "Status",
+ "common.labels.date": "Date",
+ "common.labels.time": "Time",
+ "common.labels.count": "Count",
+ "common.labels.records": "Records",
+ "validation.errors.required": "This field is required",
+ "validation.errors.min_length_8": "Field must be at least 8 characters long",
+ "validation.errors.min_length_1": "Field must be at least 1 character long",
+ "validation.errors.invalid_value": "Invalid value provided",
+ "validation.errors.generic": "Validation error occurred",
+ "connections.title": "Saved Connections",
+ "connections.subtitle": "Quick connect to your saved Salesforce connections",
+ "connections.noConnections": "No saved connections found",
+ "connections.noConnectionsMessage": "You don't have any saved connections yet. Create your first connection to get started.",
+ "connections.createFirst": "Create First Connection",
+ "connections.loading": "Loading connections...",
+ "connections.addNew": "New Connection",
+ "connections.quickConnect": "Connect",
+ "connections.connecting": "Connecting...",
+ "connections.connected": "Connected!",
+ "connections.delete": "Delete",
+ "connections.edit": "Edit",
+ "connections.notifications.saved.title": "Connection Saved",
+ "connections.notifications.saved.message": "Connection has been saved successfully",
+ "connections.notifications.deleted.title": "Connection Deleted",
+ "connections.notifications.deleted.message": "Connection has been deleted successfully",
+ "connections.notifications.connected.title": "Connected to Salesforce",
+ "connections.notifications.connected.message": "Successfully connected to Salesforce",
+ "connections.notifications.connectionError.title": "Connection Error",
+ "connections.notifications.connectionError.message": "Unable to connect to the server. Please check your internet connection and try again.",
+ "connections.lastUsed": "Last Used",
+ "connections.saveAndConnect": "Save & Connect",
+ "connections.connectionSaved": "Connection saved successfully",
+ "connections.connectionDeleted": "Connection deleted successfully",
+ "connections.connectionUpdated": "Connection updated successfully",
+ "connections.available": "Available",
+ "connections.clearAll": "Clear All",
+ "connections.clearAllData": "Clear All Data",
+ "connections.clearAllDataIcon": "Clear",
+ "connections.clearAllDataTitle": "Clear all localStorage data",
+ "connections.removeConnection": "Remove connection",
+ "connections.createFirstConnection": "Create First Connection",
+ "connections.createFirstConnectionSubtitle": "Create your first Salesforce connection to get started",
+ "connections.newConnection": "New Connection",
+ "connections.connectionsAvailable": "connection {count} available",
+ "connections.connectionsAvailableOne": "connection available",
+ "connections.connectionsAvailableMany": "connections available",
+ "connections.wizard.step1.label": "OAuth Type",
+ "connections.wizard.step1.title": "Select OAuth Type",
+ "connections.wizard.step1.description": "Choose the authentication method for your Salesforce connection",
+ "connections.wizard.step2.label": "Credentials",
+ "connections.wizard.step2.title": "Enter Credentials",
+ "connections.wizard.step2.description": "Provide your Salesforce username, password, and domain",
+ "connections.wizard.step3.label": "Advanced",
+ "connections.wizard.step3.title": "Advanced Settings",
+ "connections.wizard.step3.description": "Configure advanced connection settings",
+ "connections.wizard.previous": "Previous",
+ "connections.wizard.next": "Next",
+ "connections.oauthType": "OAuth Type",
+ "connections.connectionName": "Connection Name",
+ "connections.connectionNamePlaceholder": "Enter a name for this connection",
+ "connections.connectionNameDescription": "Leave empty to use username as the connection name",
+ "connections.username": "Username",
+ "connections.usernamePlaceholder": "Enter your Salesforce username",
+ "connections.password": "Password",
+ "connections.passwordPlaceholder": "Enter your Salesforce password",
+ "connections.environment": "Environment",
+ "connections.environmentProduction": "Production",
+ "connections.environmentSandbox": "Sandbox",
+ "connections.environmentDescription": "Choose your Salesforce environment",
+ "connections.consumerKey": "Consumer Key",
+ "connections.consumerKeyPlaceholder": "Enter your connected app consumer key",
+ "connections.consumerSecret": "Consumer Secret",
+ "connections.consumerSecretPlaceholder": "Enter your connected app consumer secret",
+ "connections.securityToken": "Security Token",
+ "connections.securityTokenPlaceholder": "Enter your Salesforce security token",
+ "connections.clientId": "Client ID",
+ "connections.clientIdPlaceholder": "Enter your connected app client ID",
+ "connections.clientSecret": "Client Secret",
+ "connections.clientSecretPlaceholder": "Enter your connected app client secret",
+ "connections.error.decryptFailed": "Failed to decrypt connection. Please check your master key.",
+ "connections.errors.decrypt_failed": "Failed to decrypt connection. Please check your master key.",
+ "connections.errors.connection_failed": "Connection failed",
+ "connections.aria.addNewConnection": "Add new connection",
+ "connections.aria.clearAllConnections": "Clear all connections",
+ "connections.aria.refreshConnections": "Refresh connections",
+ "connections.refresh": "Refresh Connections",
+ "connections.refresh.success.title": "Connections Refreshed",
+ "connections.refresh.success.message": "Connections list has been refreshed successfully",
+ "connections.refresh.error.title": "Refresh Failed",
+ "connections.refresh.error.message": "Failed to refresh connections. Please try again.",
+ "connections.errors.refresh_failed": "Refresh Failed",
+ "connections.errors.refresh_failed_message": "Failed to refresh connections. Please try again.",
+ "connections.delete.success.title": "Connection Deleted",
+ "connections.delete.success.message": "Connection has been deleted successfully",
+ "connections.delete.error.title": "Delete Failed",
+ "connections.delete.error.message": "Failed to delete connection. Please try again.",
+ "connections.errors.delete_failed": "Delete Failed",
+ "connections.errors.delete_failed_message": "Failed to delete connection. Please try again.",
+ "connections.clear_all.success.title": "All Connections Deleted",
+ "connections.clear_all.success.message": "All connections have been deleted successfully",
+ "connections.clear_all.error.title": "Delete Failed",
+ "connections.clear_all.error.message": "Failed to delete all connections. Please try again.",
+ "connections.errors.clear_all_failed": "Delete Failed",
+ "connections.errors.clear_all_failed_message": "Failed to delete all connections. Please try again.",
+ "connections.errors.invalid_master_key": "Invalid master key provided",
+ "connections.errors.invalid_auth_provider": "Invalid or non-existent authentication provider",
+ "connections.errors.cannot_retrieve_created": "Failed to retrieve created connection",
+ "connections.errors.not_found": "Connection not found",
+ "connections.errors.update_not_implemented": "Connection update not implemented",
+ "connections.errors.delete_all_failed": "Failed to delete all connections",
+ "connections.errors.favorite_not_found": "SObject is not in favorites",
+ "connections.errors.favorite_already_exists": "SObject is already in favorites for this connection",
+ "connections.messages.favorite_removed": "Favorite removed successfully",
+ "connections.renameConnection": "Rename Connection",
+ "connections.newName": "New Name",
+ "connections.enterNewName": "Enter new connection name",
+ "connections.renaming": "Renaming...",
+ "connections.save": "Save",
+ "connections.rename.success.title": "Connection Renamed",
+ "connections.rename.success.message": "Connection has been renamed successfully",
+ "connections.rename.error.title": "Rename Failed",
+ "connections.rename.error.message": "Failed to rename connection. Please try again.",
+ "connections.rename.error.invalid_name": "Invalid Name",
+ "connections.rename.error.invalid_name_message": "Please provide a valid connection name",
+ "connections.name": "Name",
+ "connections.actions": "Actions",
+ "connections.errors.cannot_retrieve_created_details": "Failed to retrieve created connection",
+ "connections.errors.cannot_retrieve_updated_details": "Failed to retrieve updated connection",
+ "connections.errors.database_operation_failed": "Database operation failed",
+ "connections.errors.credential_test_failed": "Invalid Salesforce credentials",
+ "connections.disconnect.title": "Close Connection",
+ "connections.disconnect.message": "The current Salesforce connection will be closed. This will clear all decrypted credentials from memory. Do you want to continue?",
+ "connections.disconnect.confirm": "Yes, Close Connection",
+ "connections.disconnect.cancel": "Cancel",
+ "conversations.count": "{count} conversations",
+ "conversations.agents.datapilot": "DataPilot",
+ "conversations.agents.salesforce": "Salesforce",
+ "database.errors.init_failed": "Failed to initialize database",
+ "database.errors.query_failed": "Failed to execute database query",
+ "database.errors.status_check_failed": "Failed to check database status",
+ "debug.sessionManagement": "Session Management",
+ "debug.masterKey.set": "Set",
+ "debug.masterKey.notSet": "Not Set",
+ "debug.performance": "Performance",
+ "debug.performance.fallback": "Performance",
+ "debug.memoryApiNotAvailable": "Memory API not available",
+ "debug.memoryApiNotAvailable.fallback": "Memory API not available",
+ "debug.recentLogs": "Recent Logs",
+ "debug.lastLogs": "Last {count} log entries",
+ "debug.appDebug": "App Debug",
+ "debug.debugMode": "Debug Mode",
+ "details.tab.title": "Object Details",
+ "details.tab.subtitle": "View detailed information about Salesforce objects",
+ "details.tab.search.placeholder": "Search objects...",
+ "details.tab.filter.placeholder": "Filter by type",
+ "details.tab.filter.all": "All Objects",
+ "details.tab.filter.standard": "Standard Objects",
+ "details.tab.filter.custom": "Custom Objects",
+ "details.tab.actions.export": "Export Details",
+ "details.tab.actions.settings": "Settings",
+ "details.tab.object.custom": "Custom",
+ "details.tab.object.standard": "Standard",
+ "details.tab.stats.fields": "Fields",
+ "details.tab.stats.relationships": "Relationships",
+ "details.tab.stats.triggers": "Triggers",
+ "details.tab.stats.validationRules": "Validation Rules",
+ "details.tab.permissions.title": "Object Permissions",
+ "details.tab.permissions.queryable": "Queryable",
+ "details.tab.permissions.searchable": "Searchable",
+ "details.tab.permissions.createable": "Createable",
+ "details.tab.permissions.updateable": "Updateable",
+ "details.tab.permissions.deletable": "Deletable",
+ "details.tab.fields.title": "{object} Fields",
+ "details.tab.fields.name": "Field Name",
+ "details.tab.fields.label": "Label",
+ "details.tab.fields.type": "Type",
+ "details.tab.fields.length": "Length",
+ "details.tab.fields.properties": "Properties",
+ "details.tab.field.required": "Required",
+ "details.tab.field.unique": "Unique",
+ "details.tab.field.externalId": "External ID",
+ "details.tab.field.calculated": "Calculated",
+ "details.tab.metadata.title": "Metadata Information",
+ "details.tab.metadata.lastModified": "Last Modified",
+ "details.tab.metadata.createdBy": "Created By",
+ "details.tab.empty.title": "Select an Object",
+ "details.tab.empty.description": "Choose an object from the list to view its detailed information",
+ "details.tab.objects.title": "Objects ({count})",
+ "details.tab.fields.selectObject": "Select an object to view fields",
+ "error.queryExecution.title": "Query Execution Error",
+ "error.details": "Error Details",
+ "error.details.fallback": "Error Details",
+ "error.general": "An error occurred",
+ "error.connection": "Connection failed",
+ "error.query": "Query execution failed",
+ "error.validation": "Validation error",
+ "error.unknown": "Unknown error",
+ "error.suggestions.title": "Suggestions",
+ "error.notifications.apiError.title": "API Error",
+ "error.notifications.apiError.generic": "An error occurred while communicating with the server",
+ "error.notifications.apiError.authentication": "Authentication Error",
+ "error.notifications.apiError.validation": "Validation Error",
+ "error.notifications.apiError.server": "Server Error",
+ "error.notifications.apiError.connection": "Connection Error",
+ "error.notifications.success.title": "Success",
+ "error.notifications.error.title": "Error",
+ "error.notifications.warning.title": "Warning",
+ "error.notifications.info.title": "Information",
+ "error.suggestions.syntax": "Check your SOQL syntax",
+ "error.suggestions.fields": "Verify field names exist",
+ "error.suggestions.permissions": "Check your permissions",
+ "error.suggestions.typos": "Look for typos in object or field names",
+ "error.api.server": "Server Error",
+ "error.api.request": "Request Error",
+ "i18n.errors.translation_not_found": "Translation not found",
+ "i18n.errors.language_retrieval_failed": "Failed to retrieve languages",
+ "i18n.errors.locale_retrieval_failed": "Failed to retrieve available locales",
+ "i18n.errors.locale_not_found": "Locale not found",
+ "i18n.errors.translation_retrieval_failed": "Failed to retrieve translations",
+ "i18n.errors.key_retrieval_failed": "Failed to retrieve translation key",
+ "i18n.errors.page_retrieval_failed": "Failed to retrieve available pages",
+ "i18n.errors.stats_retrieval_failed": "Failed to retrieve translation statistics",
+ "i18n.errors.language_not_found": "Language not found",
+ "i18n.errors.set_default_failed": "Failed to set default language",
+ "i18n.errors.language_code_already_exists": "Language code already exists",
+ "i18n.errors.language_creation_failed": "Failed to create language",
+ "i18n.errors.set_active_status_failed": "Failed to set language active status",
+ "logs.title": "Application Logs",
+ "logs.clear": "Clear Logs",
+ "logs.export": "Export Logs",
+ "logs.level": "Log Level",
+ "logs.timestamp": "Timestamp",
+ "logs.message": "Message",
+ "logs.messages.entry_created_successfully": "Frontend log entry created successfully",
+ "masterKey.title": "Master Key Setup",
+ "masterKey.subtitle": "Set up your master encryption key",
+ "masterKey.description": "This key will be used to encrypt and decrypt your saved connection credentials. Keep it safe and remember it well.",
+ "masterKey.setup.note": "This key is stored locally as a SHA-256 hash with salt and used to encrypt your connection credentials using AES-256-GCM with PBKDF2 key derivation. It will be required to access your saved connections.",
+ "masterKey.label": "Master Key",
+ "masterKey.input.label": "Master Key",
+ "masterKey.input.placeholder": "Enter your master key (minimum 8 characters)",
+ "masterKey.input.description": "Choose a strong, memorable key. This will be used to encrypt your connection data.",
+ "masterKey.lengthIndicator": "Length: {length}/{minLength} characters",
+ "masterKey.validIndicator": "Valid",
+ "masterKey.invalidIndicator": "Invalid",
+ "masterKey.loading": "Loading...",
+ "masterKey.initializing": "Initializing application...",
+ "masterKey.setup.title": "Setup Master Key",
+ "masterKey.setup.subtitle": "Create a master key to encrypt your sensitive data",
+ "masterKey.setup.placeholder": "Enter your master key",
+ "masterKey.setup.button": "Set Master Key",
+ "masterKey.validate.button": "Validate Master Key",
+ "masterKey.prompt.title": "Enter Master Key",
+ "masterKey.prompt.subtitle": "Please enter your master key to access saved connections",
+ "masterKey.prompt.placeholder": "Enter your master key",
+ "masterKey.prompt.button": "Unlock",
+ "masterKey.reset.button": "Reset Master Key",
+ "masterKey.error.tooShort": "Master key must be at least 8 characters long",
+ "masterKey.error.invalid": "Invalid master key. Please try again.",
+ "masterKey.invalidKey": "Invalid master key. Please try again.",
+ "masterKey.success.setup": "Master key set successfully!",
+ "masterKey.success.validate": "Master key validated successfully!",
+ "masterKey.minLengthError": "Master key must be at least 8 characters long",
+ "masterKey.reset.confirm": "Are you sure you want to reset your master key? This will permanently delete ALL saved connections, saved queries, and query history. This action cannot be undone.",
+ "masterKey.resetError": "Failed to reset master key. Please try again.",
+ "masterKey.failedAttempts": "Failed attempts: {count}/{max}",
+ "masterKey.resetAvailable": "Reset option available after {max} failed attempts",
+ "master_key.error.too_short": "Master key must be at least 8 characters long",
+ "master_key.error.key_mismatch": "Invalid master key - key does not match stored hash",
+ "master_key.error.not_found_to_delete": "Master key not found for deletion",
+ "master_key.error.invalid_key_for_deletion": "Invalid master key provided for deletion",
+ "master_key.error.internal_server_error": "Internal server error occurred while processing master key",
+ "master_key.error.failed_to_create": "Failed to create new master key",
+ "master_key.error.invalid_key": "Invalid master key provided",
+ "master_key.error.failed_to_create_with_reason": "Failed to create master key: {reason}",
+ "master_key.error.cannot_retrieve_info": "Cannot retrieve master key information",
+ "master_key.error.validation_failed": "Master key validation failed",
+ "master_key.error.not_found_to_update": "Master key not found for update",
+ "master_key.error.failed_to_update": "Failed to update master key",
+ "master_key.error.invalid_new_key": "Invalid new master key provided",
+ "master_key.error.failed_to_delete": "Failed to delete master key",
+ "master_key.errors.create_failed": "Failed to create master key",
+ "master_key.errors.invalid_key": "Invalid or weak master key",
+ "master_key.errors.invalid_key_provided": "Invalid master key provided",
+ "master_key.errors.key_validation_failed": "Key validation failed",
+ "master_key.errors.key_mismatch": "Master key mismatch",
+ "master_key.errors.key_does_not_match": "Provided key does not match existing key",
+ "master_key.errors.key_too_short": "Master key too short",
+ "master_key.errors.min_length_required": "Master key must be at least 8 characters",
+ "master_key.errors.cannot_retrieve_info": "Cannot retrieve master key information",
+ "master_key.errors.validation_failed": "Master key validation failed",
+ "master_key.errors.not_found": "Master key not found",
+ "master_key.errors.update_failed": "Failed to update master key",
+ "master_key.errors.invalid_or_weak_key": "Invalid or weak master key",
+ "master_key.errors.invalid_new_key": "Invalid new master key",
+ "master_key.errors.delete_failed": "Failed to delete master key",
+ "master_key.errors.invalid_key_for_deletion": "Invalid master key for deletion",
+ "master_key.messages.deleted_permanently": "Master key and all connections permanently deleted",
+ "query.pleaseEnterQuery": "Please enter a SOQL query to execute",
+ "query.pleaseEnterQueryMessage": "Please enter a SOQL query before executing.",
+ "query.executionFailed": "Query execution failed. Please check your SOQL syntax and try again.",
+ "query.loadMoreFailed": "Failed to load more results. Please try again.",
+ "query.recordUpdateFailed": "Failed to update record. Please try again.",
+ "query.executing": "Executing Salesforce Query",
+ "query.placeholder": "Enter your SOQL query here...",
+ "query.editor.label": "Query",
+ "query.editor.placeholder": "Enter your SOQL query here...",
+ "query.editor.execute": "Execute Query",
+ "query.editor.undo": "Undo",
+ "query.editor.redo": "Redo",
+ "query.editor.quickLoad.placeholder": "Load Saved Query",
+ "query.editor.quickLoad.loading": "Loading...",
+ "query.editor.quickLoad.noQueries": "No Saved Queries",
+ "query.notifications.executed.title": "Query Executed",
+ "query.notifications.executed.message": "Query executed successfully",
+ "query.notifications.error.title": "Query Error",
+ "query.notifications.error.message": "An error occurred while executing the query",
+ "query.editor.executeAll": "Execute Query All",
+ "query.editor.saveQuery": "Save Query",
+ "query.editor.deleteChecked": "Delete Checked",
+ "query.editor.saveModal.title": "Save Query",
+ "query.editor.saveModal.name.label": "Query Name",
+ "query.editor.saveModal.name.placeholder": "Enter query name...",
+ "query.editor.saveModal.name.description": "Choose a descriptive name for your query",
+ "query.editor.saveModal.save": "Save Query",
+ "query.editor.untitled": "Untitled Query",
+ "query.editor.dropField": "Drop field here to add to query",
+ "query.editor.tabs.query": "Query Editor",
+ "query.editor.tabs.savedQueries": "Saved Queries",
+ "query.editor.tabs.schema": "SObjects Graph",
+ "query.editor.tabs.apex": "Apex",
+ "query.editor.tabs.details": "Details",
+ "query.editor.tabs.aiAssistant": "AI Agent",
+ "query.editor.save.success.title": "Query Saved",
+ "query.editor.save.success.message": "{name} has been saved successfully",
+ "query.editor.save.error.title": "Save Failed",
+ "query.editor.save.error.invalid": "Please provide a valid query name",
+ "query.editor.save.error.no_connection": "No active connection. Please connect to Salesforce first.",
+ "query.editor.save.error.message": "Failed to save the query. Please try again.",
+ "query.executedSuccessfully": "Salesforce Query Executed Successfully",
+ "query.format.success.title": "Query Formatted",
+ "query.format.success.message": "Your SOQL query has been formatted successfully",
+ "query.format.already.title": "Query Already Formatted",
+ "query.format.already.message": "Your SOQL query is already properly formatted",
+ "query.format.error.title": "Formatting Failed",
+ "query.format.error.message": "Failed to format SOQL query. Please check syntax.",
+ "query.maxRecordsReached": "Cannot load more than {maxRecords} records. Please refine your query to get more specific results.",
+ "query.approachingMaxRecords": "Approaching maximum records limit ({current}/{max}). Consider refining your query for better performance.",
+ "query.maxRecordsReachedMessage": "This is the maximum configured result set ({maxRecords} records) that can be returned.",
+ "query.warning": "Warning",
+ "query.editor.format": "Format",
+ "query.editor.clear": "Clear",
+ "query.editor.import": "Import",
+ "query.editor.import.success.title": "Query Imported",
+ "query.editor.import.success.message": "Successfully imported query from \"{fileName}\"",
+ "query.editor.import.error.title": "Import Failed",
+ "query.editor.import.error.message": "Failed to read the selected file",
+ "query.editor.clearModal.title": "Clear Query",
+ "query.editor.clearModal.message": "Are you sure you want to clear the query?",
+ "query.editor.clearModal.warning": "This action cannot be undone.",
+ "query.editor.clearModal.confirm": "Clear Query",
+ "query.dragDrop.parseError.title": "Query Parse Error",
+ "query.dragDrop.parseError.message": "Failed to parse the current query. Please check the syntax.",
+ "query.dragDrop.sobjectNotFound.title": "SObject Not Found",
+ "query.dragDrop.sobjectNotFound.message": "SObject \"{sobjectName}\" not found in the current query. Available SObjects: {availableSObjects}",
+ "query.dragDrop.fieldExists.title": "Field Already Exists",
+ "query.dragDrop.fieldExists.message": "Field \"{fieldName}\" already exists in SObject \"{sobjectName}\". No changes made.",
+ "query.dragDrop.fieldAdded.title": "Field Added Successfully",
+ "query.dragDrop.fieldAdded.message": "Field \"{fieldName}\" has been added to SObject \"{sobjectName}\" in the query.",
+ "query.dragDrop.insertionError.title": "Field Insertion Error",
+ "query.dragDrop.insertionError.message": "Failed to insert field \"{fieldName}\" into SObject \"{sobjectName}\" using AST method. Falling back to string manipulation.",
+ "query.dragDrop.newQueryCreated.title": "New Query Created",
+ "query.dragDrop.newQueryCreated.message": "Created new query with field \"{fieldName}\" from SObject \"{sobjectName}\".",
+ "query.dragDrop.fallbackMethod.title": "Using Fallback Method",
+ "query.dragDrop.fallbackMethod.message": "AST-based insertion failed for field \"{fieldName}\" into SObject \"{sobjectName}\". Using string manipulation method.",
+ "query.dragDrop.newQueryCreatedFallback.title": "New Query Created (Fallback)",
+ "query.dragDrop.newQueryCreatedFallback.message": "Created new query with field \"{fieldName}\" from SObject \"{sobjectName}\" using fallback method.",
+ "query.dragDrop.differentSObject.title": "Different SObject Detected",
+ "query.dragDrop.differentSObject.message": "Created new query for SObject \"{sobjectName}\" with field \"{fieldName}\" (different from current query's SObject).",
+ "query.dragDrop.fieldExistsFallback.title": "Field Already Exists (Fallback)",
+ "query.dragDrop.fieldExistsFallback.message": "Field \"{fieldName}\" already exists in SObject \"{sobjectName}\". No changes made.",
+ "query.dragDrop.fieldAddedFallback.title": "Field Added (Fallback)",
+ "query.dragDrop.fieldAddedFallback.message": "Field \"{fieldName}\" has been added to SObject \"{sobjectName}\" using fallback method.",
+ "query.dragDrop.newQuery.title": "New Query Created",
+ "query.dragDrop.newQuery.message": "Created a new query for SObject \"{sobjectName}\" since the query editor was empty.",
+ "query.dragDrop.defaultQuery.title": "New Query Created",
+ "query.dragDrop.defaultQuery.message": "Created a new query for SObject \"{sobjectName}\" since no relationship was detected with the current query.",
+ "query.dragDrop.subqueryCreated.title": "Subquery Created",
+ "query.dragDrop.subqueryCreated.message": "Added field \"{fieldName}\" from related SObject \"{sobjectName}\" as a subquery using relationship \"{relationshipName}\".",
+ "query.editor.astToGraph": "Query to Graph",
+ "query.editor.astToGraph.success.title": "Graph Generated",
+ "query.editor.astToGraph.success.message": "Successfully converted query to graph visualization",
+ "query.editor.astToGraph.error.title": "No Query",
+ "query.editor.astToGraph.error.noQuery": "Please enter a query to convert to graph",
+ "query.editor.astToGraph.error.invalidQuery": "Unable to parse the query. Please check the syntax.",
+ "query.editor.astToGraph.error.conversionFailed": "Failed to convert query to graph. Please try again.",
+ "query.editor.autoSaving": "Auto-saving...",
+ "query.editor.autoSave.success": "Query auto-saved successfully",
+ "query.editor.autoSave.failed": "Auto-save failed",
+ "query.editor.autoSave.toggle.label": "Auto-save",
+ "query.editor.autoSave.toggle.enabled": "Auto-save enabled - queries will be saved after execution",
+ "query.editor.autoSave.toggle.disabled": "Auto-save disabled - click to enable",
+ "query.editor.saveModal.operation.label": "Operation",
+ "query.editor.saveModal.operation.create": "Create New Query",
+ "query.editor.saveModal.operation.update": "Update Existing Query",
+ "query.editor.saveModal.selectQuery.label": "Select Query to Update",
+ "query.editor.saveModal.selectQuery.placeholder": "Choose a saved query...",
+ "query.editor.saveModal.loading": "Loading saved queries...",
+ "query.editor.updateModal.title": "Update Query",
+ "query.editor.updateModal.name.label": "New Query Name",
+ "query.editor.updateModal.name.placeholder": "Enter new name...",
+ "query.editor.updateModal.name.description": "Enter the new name for the selected query",
+ "query.editor.updateModal.update": "Update Query",
+ "query.editor.update.success.title": "Query Updated",
+ "query.editor.update.success.message": "\"{name}\" has been updated successfully",
+ "query.editor.update.error.title": "Update Failed",
+ "query.editor.update.error.message": "Failed to update the query. Please try again.",
+ "query.editor.save.error.no_query_selected": "Please select a query to update",
+ "results.columnWidthsReset": "Column Widths Reset",
+ "results.columnWidthsResetMessage": "Column widths have been reset to optimal defaults",
+ "results.columnWidthsResetError": "Reset Failed",
+ "results.columnWidthsResetErrorMessage": "Failed to reset column widths",
+ "results.resetColumns": "Reset Columns",
+ "results.resetColumnWidthsTooltip": "Reset column widths to optimal defaults",
+ "results.loading": "Loading...",
+ "results.rightClickForDetails": "Right-click on this row to see available detail options",
+ "results.closeDetailTable": "Close this detail table",
+ "results.noDetailRelationships": "No detail relationships expanded",
+ "results.backToMasterTable": "Back to Master Table",
+ "results.detailFieldsExpanded": "{count} detail field{plural} expanded",
+ "results.noId": "No ID",
+ "results.unknown": "Unknown",
+ "results.closedWon": "Closed Won",
+ "results.closedLost": "Closed Lost",
+ "results.open": "Open",
+ "results.closed": "Closed",
+ "results.high": "High",
+ "results.hot": "Hot",
+ "results.object": "Object",
+ "results.name": "Name",
+ "results.id": "Id",
+ "results.developerName": "DeveloperName",
+ "results.label": "Label",
+ "results.type": "Type",
+ "results.value": "Value",
+ "results.text": "Text",
+ "results.goToPreviousPage": "Go to previous page",
+ "results.goToNextPage": "Go to next page",
+ "results.updateRecordFailed": "Failed to update the record. Please try again.",
+ "results.doubleClickToEditTitle": "Double-click to edit",
+ "results.title": "Query Results",
+ "results.moreRecordsAvailable": "More records available",
+ "results.editModeEnabled": "Edit Mode Enabled",
+ "results.noRecordsFound": "No Records Found",
+ "results.noRecordsFoundSubtitle": "No records were returned by your query",
+ "results.noQueryResults": "No Query Results",
+ "results.noQueryResultsSubtitle": "Execute a query to see results here",
+ "results.showingRecords": "Showing {current} of {total} records",
+ "results.loadMoreRecords": "Load More Records",
+ "results.doubleClickToEdit": "Double-click to edit",
+ "results.updating": "Updating...",
+ "results.details.label": "Details",
+ "results.export.success.title": "Export Successful",
+ "results.export.success.message": "Data exported as {format} with {count} records",
+ "results.export.failed.title": "Export Failed",
+ "results.export.failed.message": "Failed to export data. Please try again.",
+ "results.update.failed.title": "Update Failed",
+ "results.detail.close": "Close this detail table",
+ "results.maxReached": "Max Reached",
+ "results.loadMore": "Load More",
+ "results.export.noData.title": "No Data to Export",
+ "results.export.noData.message": "There are no records to export",
+ "resultsViewPage.field.null": "null",
+ "resultsViewPage.noDetailRecords": "No detail records found",
+ "resultsViewPage.title": "Query Results",
+ "resultsViewPage.badge.records": "{count} records",
+ "resultsViewPage.badge.noResults": "No results",
+ "resultsViewPage.resetColumns": "Reset Columns",
+ "resultsViewPage.noRecords": "No records found",
+ "resultsViewPage.pagination.recordsPerPage": "Records per page",
+ "resultsViewPage.pagination.text": "{from} - {to} of {totalRecords} records",
+ "resultsViewPage.noRecordsAvailable": "No records available",
+ "resultsViewPage.loadingColumns": "Loading columns...",
+ "resultsViewPage.edit.title": "Edit Record",
+ "resultsViewPage.edit.field": "Field",
+ "resultsViewPage.edit.value": "Value",
+ "resultsViewPage.showDetails": "Show details",
+ "resultsViewPage.hideDetails": "Hide details",
+ "resultsViewPage.showMasterHideDetails": "Show master (hide details)",
+ "resultsViewPage.executingQuery": "Executing Query...",
+ "resultsViewPage.pleaseWait": "Please wait while we fetch your results",
+ "resultsViewPage.previousResults": "Previous query returned {count} records",
+ "salesforce.errors.invalid_connection_params": "Invalid connection parameters",
+ "salesforce.errors.connection_failed": "Failed to establish Salesforce connection",
+ "salesforce.errors.no_connection": "No active Salesforce connection available",
+ "salesforce.errors.invalid_request_params": "Invalid request parameters",
+ "salesforce.errors.sobject_list_failed": "Failed to retrieve SObject list from Salesforce",
+ "salesforce.errors.invalid_sobject_params": "Invalid SObject name or parameters",
+ "salesforce.errors.sobject_not_found": "SObject '{resource_id}' not found or not accessible",
+ "salesforce.errors.invalid_query": "Invalid SOQL query",
+ "salesforce.errors.query_execution_failed": "Failed to execute SOQL query",
+ "salesforce.errors.invalid_next_records_id": "Invalid nextRecordsId parameter",
+ "salesforce.errors.execute_more_failed": "Failed to execute more records from previous query",
+ "salesforce.errors.invalid_user_request": "Invalid request for user information",
+ "salesforce.errors.user_info_failed": "Failed to retrieve user information from Salesforce",
+ "salesforce.errors.invalid_usage_request": "Invalid request for API usage",
+ "salesforce.errors.usage_failed": "Failed to retrieve API usage from Salesforce",
+ "salesforce.errors.status_check_failed": "Failed to check Salesforce connection status",
+ "salesforce.errors.config_failed": "Failed to retrieve Salesforce API configuration",
+ "salesforce.errors.invalid_record_params": "Invalid record update parameters",
+ "salesforce.errors.record_update_failed": "Failed to update record",
+ "salesforce.errors.logout_failed": "Failed to logout from Salesforce",
+ "saved_apex.title": "Saved Apex Code",
+ "saved_apex.subtitle": "Manage your saved Apex code with debug levels",
+ "saved_apex.search.placeholder": "Search saved Apex code...",
+ "saved_apex.filter.status.placeholder": "Filter by status",
+ "saved_apex.filter.status.all": "All Status",
+ "saved_apex.filter.status.favorite": "Favorites",
+ "saved_apex.filter.status.recent": "Recent",
+ "saved_apex.filter.type.placeholder": "Filter by type",
+ "saved_apex.filter.type.all": "All Types",
+ "saved_apex.filter.type.anonymous": "Anonymous",
+ "saved_apex.filter.type.class": "Class",
+ "saved_apex.filter.type.trigger": "Trigger",
+ "saved_apex.filter.type.interface": "Interface",
+ "saved_apex.filter.type.enum": "Enum",
+ "saved_apex.filter.type.test_class": "Test Class",
+ "saved_apex.actions.create": "Create New",
+ "saved_apex.actions.execute": "Execute",
+ "saved_apex.actions.edit": "Edit",
+ "saved_apex.actions.delete": "Delete",
+ "saved_apex.actions.toggle_favorite": "Toggle Favorite",
+ "saved_apex.tabs.all": "All ({count})",
+ "saved_apex.tabs.favorites": "Favorites ({count})",
+ "saved_apex.tabs.recent": "Recent ({count})",
+ "saved_apex.code.modified": "Modified: {date}",
+ "saved_apex.code.executions": "{count} executions",
+ "saved_apex.code.last_executed": "Last executed: {date}",
+ "saved_apex.code.status.success": "Success",
+ "saved_apex.code.status.error": "Error",
+ "saved_apex.code.status.pending": "Pending",
+ "saved_apex.error.not_found": "Saved Apex code not found",
+ "saved_apex.error.validation": "Validation error: {error}",
+ "saved_apex.error.internal": "Internal server error: {error}",
+ "saved_apex.error.execution_failed": "Apex code execution failed",
+ "saved_apex.success.created": "Saved Apex code created successfully",
+ "saved_apex.success.updated": "Saved Apex code updated successfully",
+ "saved_apex.success.deleted": "Saved Apex code deleted successfully",
+ "saved_apex.success.executed": "Saved Apex code executed successfully",
+ "saved_apex.success.favorite_toggled": "Favorite status toggled successfully",
+ "saved_apex.error.connection_required": "Connection UUID is required for this operation",
+ "saved_apex.error.invalid_connection": "Invalid connection UUID provided",
+ "saved_apex.error.connection_validation_failed": "Failed to validate connection UUID",
+ "saved_apex.error.debug_levels_invalid": "Invalid debug levels configuration",
+ "saved_apex.error.code_type_invalid": "Invalid Apex code type",
+ "saved_apex.error.name_required": "Apex code name is required",
+ "saved_apex.error.code_required": "Apex code content is required",
+ "saved_apex.error.duplicate_name": "Apex code with this name already exists",
+ "saved_apex.error.execution_timeout": "Apex code execution timed out",
+ "saved_apex.error.compilation_failed": "Apex code compilation failed",
+ "saved_apex.error.runtime_error": "Apex code runtime error occurred",
+ "saved_apex.messages.deleted_successfully": "Saved Apex code deleted successfully",
+ "saved_queries.title": "Saved Queries",
+ "saved_queries.no_connection": "No Connection",
+ "saved_queries.no_connection_message": "Please select a connection to view saved queries",
+ "saved_queries.empty": "No Saved Queries",
+ "saved_queries.empty_message": "No saved queries found for this connection. Create your first saved query!",
+ "saved_queries.refresh": "Refresh",
+ "saved_queries.executions": "executions",
+ "saved_queries.success.copied": "Query Copied",
+ "saved_queries.success.copied_message": "{name} copied to clipboard",
+ "saved_queries.success.deleted": "Query Deleted",
+ "saved_queries.success.deleted_message": "{name} has been deleted successfully",
+ "saved_queries.success.loaded": "Query Loaded",
+ "saved_queries.success.loaded_message": "{name} loaded successfully",
+ "saved_queries.errors.load_failed": "Failed to Load Saved Queries",
+ "saved_queries.errors.load_failed_message": "Failed to load saved queries. Please try again.",
+ "saved_queries.error.load_failed": "Load Failed",
+ "saved_queries.error.load_failed_message": "Failed to load saved queries",
+ "saved_queries.errors.invalid_data": "Invalid saved query data",
+ "saved_queries.errors.query_required": "Query text is required",
+ "saved_queries.errors.invalid_filter": "Invalid filter parameters",
+ "saved_queries.errors.filter_invalid": "Filter validation failed",
+ "saved_queries.errors.not_found": "Saved query not found",
+ "saved_queries.errors.invalid_identifier": "Invalid saved query identifier",
+ "saved_queries.errors.uuid_invalid": "UUID validation failed",
+ "saved_queries.errors.invalid_update_data": "Invalid update data",
+ "saved_queries.errors.update_failed": "Update operation failed",
+ "saved_queries.errors.invalid_identifier_for_deletion": "Invalid identifier for deletion",
+ "saved_queries.messages.execution_incremented": "Execution count incremented successfully",
+ "saved_queries.error.delete_failed": "Delete Failed",
+ "saved_queries.error.delete_failed_message": "Failed to delete query",
+ "saved_queries.error.duplicate_name": "Query with this name already exists",
+ "saved_queries.actions.load_to_editor": "Load to Editor",
+ "saved_queries.actions.show_query": "Show Query",
+ "saved_queries.actions.hide_query": "Hide Query",
+ "saved_queries.favorite.added": "Added to Favorites",
+ "saved_queries.favorite.removed": "Removed from Favorites",
+ "saved_queries.favorite.added_message": "{name} added to favorites",
+ "saved_queries.favorite.removed_message": "{name} removed from favorites",
+ "saved_query.error.duplicate_name": "Query with this name already exists",
+ "saved_query.error.invalid_name": "Invalid query name",
+ "saved_query.error.invalid_query_text": "Query text is required",
+ "saved_query.error.invalid_connection": "Invalid or missing connection",
+ "saved_query.error.not_found": "Saved query not found",
+ "saved_queries.success.renamed": "Query Renamed",
+ "saved_queries.success.renamed_message": "{name} renamed successfully",
+ "saved_queries.error.rename_failed": "Rename Failed",
+ "saved_queries.error.rename_failed_message": "Failed to rename query",
+ "saved_queries.error.invalid_name": "Invalid Name",
+ "saved_queries.error.invalid_name_message": "Query name cannot be empty",
+ "schema.tooltip.collapse": "Click to collapse {name}",
+ "schema.tooltip.expand": "Click to expand {name}",
+ "schema.tooltip.dragField": "Drag {name} to add to query",
+ "schema.custom": "Custom",
+ "schema.notConnected": "Not Connected",
+ "schema.connectToView": "Connect to view SObjects Tree",
+ "schema.searchPlaceholder": "Search SObjects...",
+ "schema.all": "All",
+ "schema.standard": "Standard",
+ "schema.notifications.loaded.title": "SObjects Tree Loaded",
+ "schema.notifications.loaded.message": "SObjects Tree has been loaded successfully",
+ "schema.notifications.exportSuccess.title": "Export Successful",
+ "schema.notifications.exportSuccess.message": "SObjects Tree has been exported successfully",
+ "schema.notifications.exportError.title": "Export Error",
+ "schema.notifications.exportError.message": "Failed to export SObjects Tree as PDF. Please try again.",
+ "schema.favorites": "Favorites",
+ "schema.noData": "No SObjects Tree data available",
+ "schema.noResults": "No results found",
+ "schema.cacheStatus": "Cache: {current}/{max}",
+ "schema.cacheStatus.fallback": "Cache: {current}/{max} objects",
+ "schema.picklistValues": "Picklist Values",
+ "schema.addToFavorites": "Add to favorites",
+ "schema.removeFromFavorites": "Remove from favorites",
+ "schema.togglingFavorite": "Toggling favorite...",
+ "schema.graph.title": "SObjects Graph",
+ "schema.graph.subtitle": "Drag SObjects from the tree to build interactive relationship graphs with DataPilot AI, generate detailed schema and relationship reports, export diagrams as PDF/Image, convert graphs to SOQL queries, and analyze complex SObject structures with zoom, pan, and filtering capabilities",
+ "schema.connectionNote": "Note: Make sure you have an active Salesforce connection to load SObject details",
+ "schema.loadingSObject": "Loading SObject...",
+ "schema.selectMasterParent": "Select Master Parent",
+ "schema.addChildObjects": "Add Child Objects",
+ "schema.childObjects": "Child Objects",
+ "schema.loadingRelationships": "Loading relationships...",
+ "schema.availableCount": "{count} available",
+ "schema.deleteNode": "Delete Node",
+ "schema.references": "References",
+ "schema.chooseMasterParentFor": "Choose master parent for {nodeName}",
+ "schema.sobjectAddedWithFields": "{sobjectName} added with first {initialCount} fields ({hiddenCount} more fields available but not displayed)",
+ "schema.contextMenu.addMoreFields": "Add More Fields",
+ "schema.contextMenu.selectSpecificFields": "Select Specific Fields",
+ "schema.fieldSelection.title": "Select Fields to Add",
+ "schema.fieldSelection.subtitle": "Select fields to add to {sobjectName}",
+ "schema.fieldSelection.searchPlaceholder": "๐ Search fields by name, label, or type...",
+ "schema.fieldSelection.selectAll": "Select All",
+ "schema.fieldSelection.selectNone": "Select None",
+ "schema.fieldSelection.selectedCount": "{selected} of {total} fields selected",
+ "schema.fieldSelection.addSelected": "Add {count} Selected Fields",
+ "schema.notifications.noSObjectData.title": "No SObject Data",
+ "schema.notifications.noSObjectData.message": "No metadata found for {sobjectName}",
+ "schema.notifications.fieldSelectionError.title": "Field Selection Error",
+ "schema.notifications.fieldSelectionError.message": "Failed to load fields for selection. Please try again.",
+ "schema.notifications.selectedFieldsAdded.title": "Selected Fields Added",
+ "schema.notifications.selectedFieldsAdded.message": "Added {addedFields} selected fields to {sobjectName}",
+ "schema.notifications.addSelectedFieldsError.title": "Add Fields Error",
+ "schema.notifications.addSelectedFieldsError.message": "Failed to add selected fields. Please try again.",
+ "schema.graph.empty.title": "No SObjects in SObjects Graph",
+ "schema.graph.empty.subtitle": "Drag SObjects from the tree to start building your relationship graph",
+ "schema.export.image": "Export as Image",
+ "schema.export.pdf": "Export as PDF",
+ "schema.export.exporting": "Exporting...",
+ "schema.report.export": "Export Report",
+ "schema.report.export.pdf": "Export as PDF",
+ "schema.graphToQuery": "Convert Graph to Query",
+ "schema.graphToQuerySuccess": "Graph Converted Successfully",
+ "schema.graphToQuerySuccessMessage": "Graph converted to SOQL query with {sobject} ({fields} fields, {subqueries} subqueries)",
+ "schema.graphToQueryError": "Graph Conversion Failed",
+ "schema.graphToQueryErrorMessage": "Failed to convert graph to SOQL query. Please try again.",
+ "schema.multipleParentsResolved": "Multiple Parent Relationships Resolved",
+ "schema.multipleParentsResolvedMessage": "{count} nodes had multiple parent relationships. The most recent parent was selected for each: {nodes}",
+ "schema.masterParentSelected": "Master Parent Selected",
+ "schema.masterParentSelectedMessage": "Selected {masterParent} as master parent for {childNode}",
+ "schema.notifications.sobjectAdded.title": "SObject Added to Canvas",
+ "schema.notifications.sobjectAdded.message": "{sobjectName} added with first {displayedFields} fields ({hiddenFields} more fields available but not displayed)",
+ "schema.notifications.fieldsAdded.title": "Fields Added",
+ "schema.notifications.fieldsAdded.message": "{sobjectName}: Added {addedFields} more fields ({remainingFields} remaining)",
+ "schema.notifications.allFieldsAdded.title": "All Fields Added",
+ "schema.notifications.allFieldsAdded.message": "{sobjectName} already has all {totalFields} fields displayed",
+ "schema.notifications.fieldsAddError.title": "Error Adding Fields",
+ "schema.notifications.fieldsAddError.message": "Failed to add more fields to {sobjectName}",
+ "schema.notifications.noRelationship.title": "No Relationship Found",
+ "schema.notifications.noRelationship.message": "Cannot add \"{draggedSObject}\" to the canvas. No relationship exists between \"{mainSObject}\" and \"{draggedSObject}\". Only related SObjects can be added as subqueries.",
+ "schema.notifications.childObjectAdded.title": "Child Object Added",
+ "schema.notifications.childObjectAdded.message": "{childSObjectName} added with first {displayedFields} fields ({hiddenFields} more fields available but not displayed)",
+ "schema.notifications.reverseRelationshipAdded.title": "Relationship Added",
+ "schema.notifications.reverseRelationshipAdded.message": "{sobjectName} added as subquery to {parentSObject}. Use right-click context menu to add correct child if needed.",
+ "schema.tab.fieldName": "Field Name",
+ "schema.tab.fieldLabel": "Field Label",
+ "schema.tab.type": "Type",
+ "schema.tab.properties": "Properties & Values",
+ "schema.tab.objectField": "Object / Field",
+ "schema.tab.relationshipName": "Relationship Name",
+ "schema.tab.cascadeDelete": "Cascade Delete",
+ "schema.tab.noRelationshipsFound": "No relationships found",
+ "schema.tab.noFieldsFound": "No fields found matching your filter",
+ "schema.tab.loadingFields": "Loading fields...",
+ "schema.tab.clearFilter": "Clear Filter",
+ "schema.tab.filterByName": "Filter by name, label, type, or properties...",
+ "schema.tab.filterRelationships": "Filter relationships by object, field, or relationship name...",
+ "schema.tab.exportPdf": "Export as PDF",
+ "schema.tab.collapseReport": "Collapse Report",
+ "schema.tab.expandReport": "Expand Report",
+ "schema.tab.zoomIn": "Zoom In",
+ "schema.tab.zoomOut": "Zoom Out",
+ "schema.tab.resetView": "Reset View",
+ "schema.tab.fitToCanvas": "Fit to Canvas",
+ "schema.tab.clearGraph": "Clear Graph",
+ "schema.tab.showSchemaReport": "Show Schema Report",
+ "schema.tab.showRelationshipsReport": "Show Relationships Report",
+ "schema.tab.addingSObject": "Adding SObject",
+ "schema.tab.noSObjectsInGraph": "No SObjects in Schema Graph",
+ "schema.tab.noSObjectsInGraphSubtitle": "Drag SObjects from the tree to start building your relationship graph",
+ "schema.tab.noSalesforceConnection": "No Salesforce connection available. Please connect to Salesforce first.",
+ "schema.tab.unknownError": "Unknown error",
+ "schema.tab.totalFieldsTooltip": "Total number of fields in this object",
+ "schema.tab.selectedFieldsTooltip": "Number of fields selected in the query",
+ "schema.tab.customObjectTooltip": "This is a custom Salesforce object",
+ "schema.tab.standardObjectTooltip": "This is a standard Salesforce object",
+ "schema.tab.noQueryToConvert": "No Query to Convert",
+ "schema.tab.noQueryToConvertMessage": "Please add SObjects to the canvas first before converting to SOQL query.",
+ "schema.tab.referencesTooltip": "References: {referenceNames}",
+ "schema.tab.lengthTooltip": "Maximum number of characters allowed: {length}",
+ "schema.tab.precisionTooltip": "Number format: {precision} total digits, {scale} decimal places",
+ "schema.tab.standard": "Standard",
+ "schema.tab.custom": "Custom",
+ "schema.tab.generatedOn": "Generated on: {date}",
+ "schema.tab.objectType": "Object Type: {type}",
+ "schema.tab.totalFields": "Total Fields: {total} | Filtered Fields: {filtered}",
+ "schema.tab.totalRelationships": "Total Relationships: {count}",
+ "schema.tab.relationshipsFound": "{count} relationships found",
+ "schema.tab.fieldsCount": "{filtered} of {total} fields",
+ "schema.tab.fieldsCountAll": "{filtered} of {total} fields (all metadata)",
+ "schema.tab.fieldsCountSelected": "{filtered} of {total} fields (selected)",
+ "schema.tab.matchingFilter": "matching \"{filter}\"",
+ "schema.tab.picklistValues": "Picklist Values",
+ "schema.fieldSelection.alreadySelected": "Selected",
+ "schema.fieldSelection.cancel": "Cancel",
+ "schema.fieldSelection.apply": "Apply",
+ "schema.fieldSelection.noNewFields.title": "No New Fields",
+ "schema.fieldSelection.noNewFields.message": "Please select new fields to add. Fields already in the node are not counted.",
+ "schema.fieldSelection.noChanges.title": "No Changes",
+ "schema.fieldSelection.noChanges.message": "Please select or deselect fields to make changes.",
+ "schema.notifications.noQueryError.title": "No Query Available",
+ "schema.notifications.noQueryError.message": "Please create a query in the Query tab first before adding fields.",
+ "schema.report.close": "Close Report",
+ "schema.modal.close": "Close Modal",
+ "schema.tab.actionsLabel": "Canvas Controls",
+ "app.executing": "Executing...",
+ "results.noMoreRecords": "No More Records",
+ "session.created.title": "Session Created",
+ "session.created.message": "You are now authenticated",
+ "session.ended.title": "Session Ended",
+ "session.ended.message": "You have been logged out",
+ "settings.title": "Settings",
+ "settings.language": "Language",
+ "settings.about": "About",
+ "settings.info": "Info",
+ "settings.savedConnections": "Saved Connections",
+ "settings.manage": "Manage",
+ "settings.logout": "Logout",
+ "settings.errors.retrieval_failed": "Failed to retrieve settings",
+ "settings.about.version": "Version",
+ "settings.about.buildDate": "Build Date",
+ "settings.about.license": "License",
+ "settings.about.website": "Website",
+ "settings.about.support": "Support",
+ "settings.about.feedback": "Feedback",
+ "settings.about.author": "Author",
+ "settings.about.description": "Description",
+ "sobject_cache.messages.cache_created": "โ
SObject metadata cached successfully",
+ "sobject_cache.messages.cache_updated": "๐ SObject metadata cache updated successfully",
+ "sobject_cache.messages.cache_deleted": "โ
SObject cache entry deleted successfully",
+ "sobject_cache.messages.cache_cleared": "โ
All SObject cache entries cleared successfully",
+ "sobject_cache.messages.cache_retrieved": "โ
SObject cache entry retrieved successfully",
+ "sobject_cache.errors.not_found": "โ SObject cache entry not found",
+ "sobject_cache.errors.invalid_cache_data": "โ Invalid cache data format",
+ "sobject_cache.errors.sobject_not_cached": "โ SObject is not cached for this connection",
+ "sobject_cache.errors.cache_creation_failed": "โ Failed to create SObject cache entry",
+ "sobject_cache.errors.cache_update_failed": "โ Failed to update SObject cache entry",
+ "sobject_cache.errors.cache_deletion_failed": "โ Failed to delete SObject cache entry",
+ "sobject_cache.errors.cache_retrieval_failed": "โ Failed to retrieve SObject cache entry",
+ "sobject_cache.errors.invalid_metadata": "โ Invalid SObject metadata provided",
+ "sobject_cache.errors.connection_not_found": "โ Connection not found",
+ "sobject_cache.errors.database_error": "โ Database operation failed",
+ "sobject_cache.validation.connection_uuid_required": "Connection UUID is required",
+ "sobject_cache.validation.sobject_name_required": "SObject name is required",
+ "sobject_cache.validation.metadata_required": "SObject metadata is required",
+ "sobject_cache.validation.invalid_sobject_name": "Invalid SObject name format",
+ "sobject_cache.validation.invalid_metadata_structure": "Invalid metadata structure",
+ "sobject_cache.operations.caching_metadata": "Caching SObject metadata",
+ "sobject_cache.operations.retrieving_cache": "Retrieving SObject cache entry",
+ "sobject_cache.operations.updating_cache": "Updating SObject cache entry",
+ "sobject_cache.operations.deleting_cache": "Deleting SObject cache entry",
+ "sobject_cache.operations.listing_cache": "Listing SObject cache entries",
+ "sobject_cache.operations.clearing_cache": "Clearing all SObject cache entries",
+ "sobject_cache.messages.retrieved_sobjects_successfully": "Retrieved {count} SObjects successfully",
+ "sobject_cache.messages.retrieved_metadata_successfully": "Retrieved metadata for {sobject_name} successfully",
+ "sobject_cache.messages.statistics_retrieved_successfully": "Cache statistics retrieved successfully",
+ "status.connected": "Connected",
+ "status.disconnected": "Disconnected",
+ "status.connecting": "Connecting...",
+ "status.querying": "Querying...",
+ "status.apiCalls": "API Calls: {used}/{limit} ({percentage}%)",
+ "status.describedSObjects": "Described {count} SObjects",
+ "statusBar.sobjectsCount": "Described {count} SObjects",
+ "statusBar.reload": "Reload SOBjects Tree",
+ "statusBar.reload.fallback": "Reload",
+ "statusBar.connectionId": "Connection ID ({id}...)",
+ "statusBar.apiUsage": "API Usage: {used}/{limit}",
+ "status.backend.ok": "Backend OK",
+ "status.backend.checking": "Checking...",
+ "status.backend.error": "Backend Error",
+ "toolbar.executing": "Executing...",
+ "toolbar.executing.fallback": "Executing...",
+ "toolbar.executeQuery": "Execute Query",
+ "toolbar.executeQuery.fallback": "Execute Query",
+ "toolbar.executeQueryAll": "Execute All",
+ "toolbar.executeQueryAll.fallback": "Execute All",
+ "toolbar.queryMore": "Query More",
+ "toolbar.queryMore.fallback": "Query More",
+ "toolbar.querying": "Querying...",
+ "toolbar.querying.fallback": "Querying...",
+ "toolbar.saveQuery": "Save Query",
+ "toolbar.loadQuery": "Load Query",
+ "toolbar.clearQuery": "Clear Query",
+ "toolbar.exportResults": "Export Results",
+ "toolbar.importQuery": "Import Query"
+}
\ No newline at end of file
diff --git a/dashboard/src/i18n/fr.json b/dashboard/src/i18n/fr.json
new file mode 100644
index 0000000..834c72b
--- /dev/null
+++ b/dashboard/src/i18n/fr.json
@@ -0,0 +1,1178 @@
+{
+ "aiAssistant.tab.title": "Assistant IA",
+ "aiAssistant.tab.subtitle": "Posez des questions, gรฉnรฉrez des requรชtes et obtenez des insights sur vos donnรฉes Salesforce avec l'Agent DataPilot",
+ "aiAssistant.input.title": "Demander ร l'Agent DataPilot",
+ "aiAssistant.input.description": "Posez des questions sur les donnรฉes Salesforce, gรฉnรฉrez des requรชtes SOQL ou obtenez des insights avec l'assistance IA",
+ "aiAssistant.input.placeholder": "Demandez ร l'Agent DataPilot n'importe quoi sur vos donnรฉes Salesforce, comme 'Montrez-moi tous les champs Account' ou 'Gรฉnรฉrez une requรชte pour les opportunitรฉs rรฉcentes'",
+ "aiAssistant.input.generating": "Traitement...",
+ "aiAssistant.input.generate": "Demander ร l'Agent DataPilot",
+ "aiAssistant.conversations.title": "Conversations rรฉcentes",
+ "aiAssistant.conversations.empty.title": "Aucune conversation pour le moment",
+ "aiAssistant.conversations.empty.description": "Commencez ร discuter avec l'Agent DataPilot pour voir vos conversations ici",
+ "aiAssistant.recentConversations.title": "Conversations rรฉcentes",
+ "aiAssistant.recentConversations.empty": "Aucune conversation rรฉcente pour le moment. Commencez ร discuter avec l'Agent DataPilot !",
+ "aiAssistant.recentConversations.conversation": "Conversation",
+ "aiAssistant.recentConversations.reuse": "Rรฉutiliser le message",
+ "aiAssistant.conversation.request": "Votre question",
+ "aiAssistant.conversation.response": "Rรฉponse de l'agent",
+ "aiAssistant.conversation.useQuery": "Utiliser cette requรชte dans l'รฉditeur",
+ "aiAssistant.suggestions.title": "Suggestions de requรชtes",
+ "aiAssistant.suggestion.category.basic": "Basique",
+ "aiAssistant.suggestion.category.advanced": "Avancรฉ",
+ "aiAssistant.suggestion.category.analytics": "Analytique",
+ "aiAssistant.suggestion.category.custom": "Personnalisรฉ",
+ "aiAssistant.suggestion.difficulty.easy": "Facile",
+ "aiAssistant.suggestion.difficulty.medium": "Moyen",
+ "aiAssistant.suggestion.difficulty.hard": "Difficile",
+ "aiAssistant.suggestion.tags": "รtiquettes : {tags}",
+ "aiAssistant.suggestion.useQuery": "Utiliser cette requรชte",
+ "aiAssistant.status.connected": "Agent DataPilot en ligne",
+ "aiAssistant.status.offline": "Agent DataPilot hors ligne",
+ "aiAssistant.status.websocket.connected": "WebSocket connectรฉ",
+ "aiAssistant.status.websocket.connecting": "Connexion WebSocket...",
+ "aiAssistant.status.websocket.chat": "Chat en temps rรฉel avec l'Agent DataPilot (WebSocket)",
+ "aiAssistant.status.websocket.connecting.chat": "Connexion au chat en temps rรฉel...",
+ "aiAssistant.status.unavailable": "L'Agent DataPilot n'est pas disponible. Veuillez vรฉrifier votre connexion.",
+ "aiAssistant.status.error.title": "Erreur de l'Agent DataPilot",
+ "aiAssistant.status.sending": "Envoi...",
+ "aiAssistant.realtime.label": "Chat en temps rรฉel :",
+ "aiAssistant.notifications.noConversations.title": "Aucune conversation disponible",
+ "aiAssistant.notifications.noConversations.message": "Veuillez d'abord crรฉer une nouvelle conversation avant de discuter avec l'Agent IA.",
+ "aiAssistant.notifications.noConversationSelected.title": "Aucune conversation sรฉlectionnรฉe",
+ "aiAssistant.notifications.noConversationSelected.message": "Veuillez sรฉlectionner une conversation dans la liste ร droite avant de poser une question.",
+ "aiAssistant.metadata.intent": "Intention :",
+ "aiAssistant.metadata.understood": "Compris :",
+ "aiAssistant.metadata.confidence": "Confiance :",
+ "aiAssistant.metadata.execution_time": "ms",
+ "aiAssistant.metadata.tools_used": "Outils utilisรฉs :",
+ "aiAssistant.metadata.actions": "Actions :",
+ "conversations.title": "Conversations rรฉcentes",
+ "conversations.search.placeholder": "Rechercher des conversations...",
+ "conversations.empty.title": "Aucune conversation pour le moment",
+ "conversations.empty.subtitle": "Commencez ร discuter avec l'Agent DataPilot pour voir vos conversations ici",
+ "conversations.empty.search": "Aucune conversation trouvรฉe",
+ "conversations.empty.searchSubtitle": "Essayez d'ajuster vos termes de recherche",
+ "conversations.actions.refresh": "Actualiser",
+ "conversations.actions.create": "Nouveau",
+ "conversations.actions.createFirst": "Crรฉer la premiรจre conversation",
+ "conversations.actions.reuse": "Rรฉutiliser le message",
+ "conversations.loading": "Chargement des conversations...",
+ "conversations.showing": "Affichage de {count} sur {total}",
+ "conversations.create.title": "Crรฉer une nouvelle conversation",
+ "conversations.create.titleLabel": "Titre de la conversation",
+ "conversations.create.titlePlaceholder": "Saisissez un titre pour votre conversation (optionnel)",
+ "conversations.create.descriptionLabel": "Description (optionnelle)",
+ "conversations.create.descriptionPlaceholder": "Saisissez une description pour votre conversation",
+ "conversations.create.tagsLabel": "รtiquettes (optionnelles)",
+ "conversations.create.tagsPlaceholder": "Saisissez des รฉtiquettes sรฉparรฉes par des virgules (ex. salesforce, soql, mรฉtadonnรฉes)",
+ "conversations.create.initialMessage": "Message initial",
+ "conversations.create.create": "Crรฉer la conversation",
+ "conversations.edit.title": "Modifier la conversation",
+ "conversations.edit.update": "Mettre ร jour la conversation",
+ "aiAssistant.recentConversations.noConnection": "Connectez-vous ร Salesforce pour gรฉrer les conversations",
+ "aiAssistant.smartResponse.intentUnderstanding": "Comprรฉhension de l'intention",
+ "aiAssistant.smartResponse.intentNotAvailable": "Analyse d'intention non disponible",
+ "aiAssistant.smartResponse.actionsTaken": "Actions effectuรฉes :",
+ "aiAssistant.smartResponse.confidence.high": "รlevรฉe",
+ "aiAssistant.smartResponse.confidence.medium": "Moyenne",
+ "aiAssistant.smartResponse.confidence.low": "Faible",
+ "aiAssistant.smartResponse.confidence.unknown": "Inconnue",
+ "aiAssistant.smartResponse.confidence.label": "Confiance",
+ "aiAssistant.smartResponse.fields": "Champs",
+ "aiAssistant.smartResponse.showMore": "Afficher plus",
+ "aiAssistant.smartResponse.remaining": "restants",
+ "aiAssistant.smartResponse.fieldName": "Nom du champ",
+ "aiAssistant.smartResponse.label": "รtiquette",
+ "aiAssistant.smartResponse.type": "Type",
+ "aiAssistant.smartResponse.required": "Obligatoire",
+ "aiAssistant.smartResponse.description": "Description",
+ "aiAssistant.smartResponse.noDescription": "Aucune description",
+ "aiAssistant.smartResponse.foundObjects": "{count} objets liรฉs trouvรฉs. Sรฉlectionnez-en un pour voir les dรฉtails.",
+ "aiAssistant.smartResponse.sampleFields": "Champs d'exemple :",
+ "aiAssistant.smartResponse.moreFields": "+{count} autres champs",
+ "aiAssistant.smartResponse.queryResults": "Rรฉsultats de la requรชte",
+ "aiAssistant.smartResponse.records": "enregistrements",
+ "aiAssistant.smartResponse.generatedSoql": "Requรชte SOQL gรฉnรฉrรฉe :",
+ "aiAssistant.smartResponse.sampleRecords": "Enregistrements d'exemple",
+ "aiAssistant.streamingChat": "Chat en streaming",
+ "aiAssistant.smartResponse.loadMore": "Charger plus",
+ "aiAssistant.smartResponse.clarificationTitle": "Clarification nรฉcessaire",
+ "aiAssistant.smartResponse.intentUnderstood": "Intention comprise :",
+ "aiAssistant.smartResponse.clarificationQuestion": "Question de clarification :",
+ "aiAssistant.smartResponse.pleaseChooseOptions": "Veuillez choisir l'une des options suivantes :",
+ "aiAssistant.smartResponse.clarificationReason": "Veuillez sรฉlectionner quel objet vous souhaitez explorer.",
+ "aiAssistant.smartResponse.fieldsAvailable": "champs disponibles",
+ "aiAssistant.smartResponse.explore": "Explorer",
+ "aiAssistant.smartResponse.objectRelationships": "Relations d'objets",
+ "aiAssistant.smartResponse.parentRelationships": "Relations parentes",
+ "aiAssistant.smartResponse.childRelationships": "Relations enfants",
+ "aiAssistant.smartResponse.lookupRelationships": "Relations de recherche",
+ "aiAssistant.smartResponse.relationshipName": "Nom de la relation",
+ "aiAssistant.smartResponse.childObject": "Objet enfant",
+ "aiAssistant.smartResponse.referenceTo": "Rรฉfรฉrence vers",
+ "aiAssistant.smartResponse.fieldDetails": "Dรฉtails du champ",
+ "aiAssistant.smartResponse.basicInformation": "Informations de base",
+ "aiAssistant.smartResponse.fieldProperties": "Propriรฉtรฉs du champ",
+ "aiAssistant.smartResponse.length": "Longueur",
+ "aiAssistant.smartResponse.precision": "Prรฉcision",
+ "aiAssistant.smartResponse.scale": "รchelle",
+ "aiAssistant.smartResponse.unique": "Unique",
+ "aiAssistant.smartResponse.calculated": "Calculรฉ",
+ "aiAssistant.smartResponse.createable": "Crรฉable",
+ "aiAssistant.smartResponse.updateable": "Modifiable",
+ "aiAssistant.smartResponse.formula": "Formule",
+ "aiAssistant.smartResponse.picklistValues": "Valeurs de liste de sรฉlection",
+ "aiAssistant.smartResponse.value": "Valeur",
+ "aiAssistant.smartResponse.active": "Actif",
+ "aiAssistant.smartResponse.default": "Par dรฉfaut",
+ "aiAssistant.smartResponse.viewAllValues": "Voir toutes les valeurs",
+ "aiAssistant.smartResponse.andMoreValues": "... et {count} autres valeurs",
+ "aiAssistant.smartResponse.picklistValuesDescription": "Toutes les valeurs de liste de sรฉlection disponibles pour ce champ",
+ "aiAssistant.smartResponse.metadataInformation": "Informations de mรฉtadonnรฉes",
+ "aiAssistant.smartResponse.dataResults": "Rรฉsultats de donnรฉes",
+ "aiAssistant.smartResponse.suggestions": "Suggestions",
+ "aiAssistant.smartResponse.noDataToDisplay": "Aucune donnรฉe disponible ร afficher",
+ "aiAssistant.smartResponse.unknownResponseType": "Type de rรฉponse inconnu : {type}",
+ "aiAssistant.smartResponse.noRelationshipFound": "Aucun {relationshipName} trouvรฉ",
+ "aiAssistant.smartResponse.detectedObject": "Objet dรฉtectรฉ :",
+ "aiAssistant.smartResponse.noRelationshipsFound": "Aucune relation trouvรฉe pour cet objet.",
+ "aiAssistant.smartResponse.closeModal": "Fermer",
+ "conversations.notifications.created.title": "Conversation crรฉรฉe",
+ "conversations.notifications.created.message": "Nouvelle conversation \"{title}\" a รฉtรฉ crรฉรฉe avec succรจs",
+ "conversations.notifications.updated.title": "Conversation mise ร jour",
+ "conversations.notifications.updated.message": "La conversation \"{title}\" a รฉtรฉ mise ร jour avec succรจs",
+ "conversations.notifications.deleted.title": "Conversation supprimรฉe",
+ "conversations.notifications.deleted.message": "La conversation \"{title}\" a รฉtรฉ supprimรฉe avec succรจs",
+ "conversations.notifications.error.create.title": "รchec de la crรฉation de conversation",
+ "conversations.notifications.error.create.message": "รchec de la crรฉation de la conversation. Veuillez rรฉessayer",
+ "conversations.notifications.error.update.title": "รchec de la mise ร jour de conversation",
+ "conversations.notifications.error.update.message": "รchec de la mise ร jour de la conversation. Veuillez rรฉessayer",
+ "conversations.notifications.error.delete.title": "รchec de la suppression de conversation",
+ "conversations.notifications.error.delete.message": "รchec de la suppression de la conversation. Veuillez rรฉessayer",
+ "conversations.messages.summary_updated": "Rรฉsumรฉ de la conversation mis ร jour avec succรจs",
+ "aiAssistant.thinking": "L'agent IA rรฉflรฉchit...",
+ "aiAssistant.thinkingProcess": "Processus de rรฉflexion IA :",
+ "aiAssistant.response": "Rรฉponse :",
+ "aiAssistant.liveResponse": "Rรฉponse en direct :",
+ "aiAssistant.finalResponse": "Rรฉponse finale :",
+ "aiAssistant.processingRequest": "Traitement de votre demande...",
+ "aiAssistant.thinkingLabel": "Rรฉflexion IA :",
+ "aiAssistant.errors.timeout": "Opรฉration expirรฉe. Veuillez rรฉessayer.",
+ "aiAssistant.errors.noConversationSelected": "Veuillez sรฉlectionner une conversation avant de poser une question",
+ "aiAssistant.errors.processFailed": "รchec du traitement de la requรชte",
+ "aiAssistant.errors.loadMessages.title": "Erreur de chargement des messages",
+ "aiAssistant.errors.loadMessages.message": "รchec du chargement des messages de conversation. Veuillez rรฉessayer.",
+ "aiAssistant.processingSteps.connecting": "Connexion ร l'agent IA...",
+ "aiAssistant.processingSteps.sending": "Envoi du message ร l'agent IA...",
+ "aiAssistant.processingSteps.waiting": "En attente de la rรฉponse IA...",
+ "aiAssistant.processingSteps.analyzing": "Analyse de votre demande...",
+ "aiAssistant.processingSteps.processing": "Traitement de la rรฉponse...",
+ "aiAssistant.processingSteps.finalizing": "Finalisation de la rรฉponse...",
+ "aiAssistant.thinkingOverlay.processingRequest": "Traitement de votre demande...",
+ "aiAssistant.thinkingOverlay.thinking": "Rรฉflexion",
+ "aiAssistant.thinkingOverlay.progress": "Progrรจs",
+ "aiAssistant.thinkingOverlay.currentStep": "รtape actuelle",
+ "aiAssistant.thinkingOverlay.steps.analyzingRequest": "Analyse de la demande",
+ "aiAssistant.thinkingOverlay.steps.selectingTools": "Sรฉlection des outils",
+ "aiAssistant.thinkingOverlay.steps.queryingData": "Interrogation des donnรฉes",
+ "aiAssistant.thinkingOverlay.steps.processingLogic": "Traitement de la logique",
+ "aiAssistant.thinkingOverlay.steps.generatingResponse": "Gรฉnรฉration de la rรฉponse",
+ "aiAssistant.thinkingOverlay.toolSelection.waiting": "Analyse de votre demande...",
+ "aiAssistant.thinkingOverlay.toolSelection.analyzing": "รvaluation des outils disponibles...",
+ "aiAssistant.thinkingOverlay.toolSelection.selecting": "Sรฉlection de la meilleure approche...",
+ "aiAssistant.thinkingOverlay.toolSelection.deciding": "Dรฉcision sur les outils ร utiliser...",
+ "aiAssistant.thinkingOverlay.toolSelection.preparing": "Prรฉparation de l'exรฉcution des outils...",
+ "aiAssistant.thinkingOverlay.tools.searchObjects": "Rechercher des objets",
+ "aiAssistant.thinkingOverlay.tools.getObjectDetails": "Obtenir les dรฉtails de l'objet",
+ "aiAssistant.thinkingOverlay.tools.findRelationships": "Trouver les relations",
+ "aiAssistant.thinkingOverlay.tools.queryData": "Interroger les donnรฉes",
+ "aiAssistant.chat.you": "Vous",
+ "aiAssistant.chat.ai": "Assistant IA",
+ "common.time.created": "Crรฉรฉ le",
+ "common.time.updated": "Mis ร jour",
+ "apex.title": "Gestionnaire de code Apex",
+ "apex.subtitle": "Visualiser et gรฉrer les classes et dรฉclencheurs Apex",
+ "apex.coming_soon.title": "Prochainement dans la prochaine version",
+ "apex.coming_soon.message": "La fonctionnalitรฉ Apex est actuellement en dรฉveloppement et sera disponible dans la prochaine version. Cela inclura l'exรฉcution de code Apex, la gestion d'Apex enregistrรฉ et l'intรฉgration de mรฉtadonnรฉes Salesforce.",
+ "apex.search.placeholder": "Rechercher du code Apex...",
+ "apex.filter.status.placeholder": "Filtrer par statut",
+ "apex.filter.status.all": "Tous les statuts",
+ "apex.filter.status.active": "Actif",
+ "apex.filter.status.inactive": "Inactif",
+ "apex.actions.deploy": "Dรฉployer",
+ "apex.actions.export": "Exporter",
+ "apex.tabs.classes": "Classes ({count})",
+ "apex.tabs.triggers": "Dรฉclencheurs ({count})",
+ "apex.class.modified": "Modifiรฉ : {date}",
+ "apex.class.test": "Test",
+ "apex.class.testClass": "Classe de test",
+ "apex.class.meta": "Derniรจre modification : {modified} | Crรฉรฉ par : {createdBy}",
+ "apex.class.code": "Code",
+ "apex.trigger.object": "Objet : {object}",
+ "apex.trigger.meta": "Derniรจre modification : {modified} | Crรฉรฉ par : {createdBy}",
+ "apex.trigger.code": "Code",
+ "apex.editCode": "Modifier le code Apex",
+ "apex.codeType": "Type de code",
+ "apex.anonymous": "Anonyme",
+ "apex.class": "Classe",
+ "apex.trigger": "Dรฉclencheur",
+ "apex.interface": "Interface",
+ "apex.enum": "รnumรฉration",
+ "apex.testClass": "Classe de test",
+ "apex.code": "Code Apex",
+ "apex.createNew": "Crรฉer un nouveau code Apex",
+ "apex.codePlaceholder": "Saisissez votre code Apex ici",
+ "apex.markFavorite": "Marquer comme favori",
+ "apex.status.active": "Actif",
+ "apex.status.inactive": "Inactif",
+ "apex.error.no_connection": "Aucune connexion Salesforce active. Veuillez vous connecter ร Salesforce d'abord.",
+ "apex.error.validation": "Erreur de validation Apex : {error}",
+ "apex.error.internal": "Erreur interne du serveur lors de l'opรฉration Apex : {error}",
+ "apex.error.execution_failed": "รchec de l'exรฉcution du code Apex",
+ "apex.success.execution": "Code Apex exรฉcutรฉ avec succรจs",
+ "apex.error.compilation_failed": "รchec de la compilation Apex",
+ "apex.error.test_execution_failed": "รchec de l'exรฉcution du test Apex",
+ "apex.error.limits_retrieval_failed": "รchec de la rรฉcupรฉration des limites Apex",
+ "apex.error.status_retrieval_failed": "รchec de la rรฉcupรฉration du statut de compilation",
+ "apex.messages.execution_completed": "Exรฉcution Apex terminรฉe avec succรจs",
+ "apex.messages.execution_failed": "รchec de l'exรฉcution Apex",
+ "apex.filter.codeType.interface": "Interface",
+ "apex.filter.codeType.enum": "รnumรฉration",
+ "apex.filter.codeType.test_class": "Classe de test",
+ "apex.form.name": "Nom",
+ "apex.form.namePlaceholder": "Saisissez le nom du code Apex",
+ "apex.form.description": "Description",
+ "apex.form.descriptionPlaceholder": "Description optionnelle",
+ "apex.form.tags": "รtiquettes",
+ "apex.form.tagsPlaceholder": "รtiquettes sรฉparรฉes par des virgules",
+ "apex.form.favorite": "Favori",
+ "apex.form.codePlaceholder": "Saisissez votre code Apex ici",
+ "apex.execution.results": "Rรฉsultats d'exรฉcution Apex",
+ "app.title": "Pilote de Donnรฉes",
+ "app.name": "DataPilot",
+ "app.subtitle": "Votre pilote IA pour la navigation des donnรฉes Salesforce",
+ "app.description": "รditeur de requรชtes SOQL avancรฉ avec intelligence alimentรฉe par l'IA, exploration de schรฉma, analyse de rรฉsultats et navigation intelligente des donnรฉes",
+ "app.feature.secure": "Sรฉcurisรฉ",
+ "app.feature.fast": "Rapide",
+ "app.feature.professional": "Professionnel",
+ "app.feature.smart": "Intelligent",
+ "app.feature.modern": "Moderne",
+ "app.initializing": "Initialisation...",
+ "app.version": "v1.0.0",
+ "app.unknownInitializationError": "Erreur d'initialisation inconnue",
+ "app.failedToReloadSchema": "รchec du rechargement du schรฉma",
+ "app.initializationFailed": "รchec de l'initialisation",
+ "app.checkingAuth": "Vรฉrification du statut d'authentification...",
+ "app.title.fallback": "DataPilot",
+ "app.subtitle.fallback": "Votre pilote IA pour la navigation des donnรฉes Salesforce",
+ "app.description.fallback": "Connectez-vous ร votre organisation Salesforce et naviguez dans les donnรฉes avec des requรชtes SOQL alimentรฉes par l'IA",
+ "app.expandSchemaTree": "Dรฉvelopper l'arbre SObjects",
+ "app.collapseSchemaTree": "Rรฉduire l'arbre SObjects",
+ "app.expandResultsMiddle": "Dรฉvelopper les rรฉsultats (Moyen)",
+ "app.collapseResultsMiddle": "Rรฉduire les rรฉsultats (Moyen)",
+ "app.expandResultsFull": "Dรฉvelopper les rรฉsultats (Complet)",
+ "app.sobjectsTree": "Arbre SObjects",
+ "app.backendServerRequired": "Serveur Backend Requis",
+ "auth_providers.errors.not_found": "Fournisseur d'authentification non trouvรฉ",
+ "auth_providers.errors.invalid_data": "Donnรฉes de fournisseur d'authentification invalides",
+ "auth_providers.errors.invalid_update_data": "Donnรฉes de mise ร jour de fournisseur d'authentification invalides",
+ "auth_providers.errors.invalid_provider_type": "Type de fournisseur invalide",
+ "auth_providers.errors.must_be_one_of": "Doit รชtre l'un de",
+ "auth_providers.notifications.authError.title": "Erreur d'authentification",
+ "auth_providers.notifications.authError.message": "รchec de l'authentification. Veuillez vรฉrifier vos identifiants et rรฉessayer.",
+ "common.close": "Fermer",
+ "common.settings": "Paramรจtres",
+ "common.general": "Gรฉnรฉral",
+ "common.appearance": "Apparence",
+ "common.language": "Langue",
+ "common.advanced": "Avancรฉ",
+ "common.retry": "Rรฉessayer",
+ "common.cancel": "Annuler",
+ "common.save": "Enregistrer",
+ "common.loading": "Chargement...",
+ "common.yes": "Oui",
+ "common.no": "Non",
+ "common.resetToDefaults": "Rรฉinitialiser aux valeurs par dรฉfaut",
+ "common.short": "Court",
+ "common.long": "Long",
+ "common.dateFormat": "Format de date",
+ "common.note": "Note",
+ "common.backToApp": "Retour ร l'application",
+ "common.notConnected": "Non connectรฉ",
+ "common.configure": "Configurer",
+ "common.status.success": "Succรจs",
+ "common.status.error": "Erreur",
+ "common.status.running": "En cours",
+ "common.status.connected": "Connectรฉ",
+ "common.status.disconnected": "Dรฉconnectรฉ",
+ "common.status.connecting": "Connexion...",
+ "common.status.querying": "Requรชte en cours...",
+ "common.filter.all": "Tout",
+ "common.filter.standard": "Standard",
+ "common.filter.custom": "Personnalisรฉ",
+ "common.actions.export": "Exporter",
+ "common.actions.refresh": "Actualiser",
+ "common.actions.clear": "Effacer",
+ "common.actions.delete": "Supprimer",
+ "common.actions.edit": "Modifier",
+ "common.actions.copy": "Copier",
+ "common.actions.execute": "Exรฉcuter",
+ "common.actions.load": "Charger",
+ "common.actions.save": "Enregistrer",
+ "common.actions.cancel": "Annuler",
+ "common.actions.retry": "Rรฉessayer",
+ "common.errors.error": "Erreur",
+ "common.time.justNow": "ร l'instant",
+ "common.time.hoursAgo": "{hours}h",
+ "common.time.daysAgo": "{days}j",
+ "common.placeholder.search": "Rechercher...",
+ "common.placeholder.filter": "Filtrer...",
+ "common.labels.title": "Titre",
+ "common.labels.subtitle": "Sous-titre",
+ "common.labels.name": "Nom",
+ "common.labels.type": "Type",
+ "common.labels.description": "Description",
+ "common.labels.timestamp": "Horodatage",
+ "common.labels.message": "Message",
+ "common.labels.details": "Dรฉtails",
+ "common.labels.actions": "Actions",
+ "common.labels.status": "Statut",
+ "common.labels.date": "Date",
+ "common.labels.time": "Heure",
+ "common.labels.count": "Nombre",
+ "common.labels.records": "Enregistrements",
+ "validation.errors.required": "Ce champ est obligatoire",
+ "validation.errors.min_length_8": "Le champ doit contenir au moins 8 caractรจres",
+ "validation.errors.min_length_1": "Le champ doit contenir au moins 1 caractรจre",
+ "validation.errors.invalid_value": "Valeur invalide fournie",
+ "validation.errors.generic": "Une erreur de validation s'est produite",
+ "connections.title": "Connexions enregistrรฉes",
+ "connections.subtitle": "Connexion rapide ร vos connexions Salesforce enregistrรฉes",
+ "connections.noConnections": "Aucune connexion enregistrรฉe trouvรฉe",
+ "connections.noConnectionsMessage": "Vous n'avez pas encore de connexions enregistrรฉes. Crรฉez votre premiรจre connexion pour commencer.",
+ "connections.createFirst": "Crรฉer la premiรจre connexion",
+ "connections.loading": "Chargement des connexions...",
+ "connections.addNew": "Nouvelle connexion",
+ "connections.quickConnect": "Se connecter",
+ "connections.connecting": "Connexion...",
+ "connections.connected": "Connectรฉ !",
+ "connections.delete": "Supprimer",
+ "connections.edit": "Modifier",
+ "connections.notifications.saved.title": "Connexion enregistrรฉe",
+ "connections.notifications.saved.message": "La connexion a รฉtรฉ enregistrรฉe avec succรจs",
+ "connections.notifications.deleted.title": "Connexion supprimรฉe",
+ "connections.notifications.deleted.message": "La connexion a รฉtรฉ supprimรฉe avec succรจs",
+ "connections.notifications.connected.title": "Connectรฉ ร Salesforce",
+ "connections.notifications.connected.message": "Connexion rรฉussie ร Salesforce",
+ "connections.notifications.connectionError.title": "Erreur de connexion",
+ "connections.notifications.connectionError.message": "Impossible de se connecter au serveur. Veuillez vรฉrifier votre connexion internet et rรฉessayer.",
+ "connections.lastUsed": "Derniรจre utilisation",
+ "connections.saveAndConnect": "Enregistrer et se connecter",
+ "connections.connectionSaved": "Connexion enregistrรฉe avec succรจs",
+ "connections.connectionDeleted": "Connexion supprimรฉe avec succรจs",
+ "connections.connectionUpdated": "Connexion mise ร jour avec succรจs",
+ "connections.available": "Disponible",
+ "connections.clearAll": "Tout effacer",
+ "connections.clearAllData": "Effacer toutes les donnรฉes",
+ "connections.clearAllDataIcon": "Effacer",
+ "connections.clearAllDataTitle": "Effacer toutes les donnรฉes localStorage",
+ "connections.removeConnection": "Supprimer la connexion",
+ "connections.createFirstConnection": "Crรฉer la premiรจre connexion",
+ "connections.createFirstConnectionSubtitle": "Crรฉez votre premiรจre connexion Salesforce pour commencer",
+ "connections.newConnection": "Nouvelle connexion",
+ "connections.connectionsAvailable": "connexion {count} disponible",
+ "connections.connectionsAvailableOne": "connexion disponible",
+ "connections.connectionsAvailableMany": "connexions disponibles",
+ "connections.wizard.step1.label": "Type OAuth",
+ "connections.wizard.step1.title": "Sรฉlectionner le type OAuth",
+ "connections.wizard.step1.description": "Choisissez la mรฉthode d'authentification pour votre connexion Salesforce",
+ "connections.wizard.step2.label": "Identifiants",
+ "connections.wizard.step2.title": "Saisir les identifiants",
+ "connections.wizard.step2.description": "Fournissez votre nom d'utilisateur, mot de passe et domaine Salesforce",
+ "connections.wizard.step3.label": "Avancรฉ",
+ "connections.wizard.step3.title": "Paramรจtres avancรฉs",
+ "connections.wizard.step3.description": "Configurez les paramรจtres de connexion avancรฉs",
+ "connections.wizard.previous": "Prรฉcรฉdent",
+ "connections.wizard.next": "Suivant",
+ "connections.oauthType": "Type OAuth",
+ "connections.connectionName": "Nom de la connexion",
+ "connections.connectionNamePlaceholder": "Saisissez un nom pour cette connexion",
+ "connections.connectionNameDescription": "Laissez vide pour utiliser le nom d'utilisateur comme nom de connexion",
+ "connections.username": "Nom d'utilisateur",
+ "connections.usernamePlaceholder": "Saisissez votre nom d'utilisateur Salesforce",
+ "connections.password": "Mot de passe",
+ "connections.passwordPlaceholder": "Saisissez votre mot de passe Salesforce",
+ "connections.environment": "Environnement",
+ "connections.environmentProduction": "Production",
+ "connections.environmentSandbox": "Sandbox",
+ "connections.environmentDescription": "Choisissez votre environnement Salesforce",
+ "connections.consumerKey": "Clรฉ consommateur",
+ "connections.consumerKeyPlaceholder": "Saisissez la clรฉ consommateur de votre application connectรฉe",
+ "connections.consumerSecret": "Secret consommateur",
+ "connections.consumerSecretPlaceholder": "Saisissez le secret consommateur de votre application connectรฉe",
+ "connections.securityToken": "Jeton de sรฉcuritรฉ",
+ "connections.securityTokenPlaceholder": "Saisissez votre jeton de sรฉcuritรฉ Salesforce",
+ "connections.clientId": "ID client",
+ "connections.clientIdPlaceholder": "Saisissez l'ID client de votre application connectรฉe",
+ "connections.clientSecret": "Secret client",
+ "connections.clientSecretPlaceholder": "Saisissez le secret client de votre application connectรฉe",
+ "connections.error.decryptFailed": "รchec du dรฉcryptage de la connexion. Veuillez vรฉrifier votre clรฉ maรฎtre.",
+ "connections.errors.decrypt_failed": "รchec du dรฉcryptage de la connexion. Veuillez vรฉrifier votre clรฉ maรฎtre.",
+ "connections.errors.connection_failed": "รchec de la connexion",
+ "connections.aria.addNewConnection": "Ajouter une nouvelle connexion",
+ "connections.aria.clearAllConnections": "Effacer toutes les connexions",
+ "connections.aria.refreshConnections": "Actualiser les connexions",
+ "connections.refresh": "Actualiser les connexions",
+ "connections.refresh.success.title": "Connexions actualisรฉes",
+ "connections.refresh.success.message": "La liste des connexions a รฉtรฉ actualisรฉe avec succรจs",
+ "connections.refresh.error.title": "รchec de l'actualisation",
+ "connections.refresh.error.message": "รchec de l'actualisation des connexions. Veuillez rรฉessayer.",
+ "connections.errors.refresh_failed": "รchec de l'actualisation",
+ "connections.errors.refresh_failed_message": "รchec de l'actualisation des connexions. Veuillez rรฉessayer.",
+ "connections.delete.success.title": "Connexion supprimรฉe",
+ "connections.delete.success.message": "La connexion a รฉtรฉ supprimรฉe avec succรจs",
+ "connections.delete.error.title": "รchec de la suppression",
+ "connections.delete.error.message": "รchec de la suppression de la connexion. Veuillez rรฉessayer.",
+ "connections.errors.delete_failed": "รchec de la suppression",
+ "connections.errors.delete_failed_message": "รchec de la suppression de la connexion. Veuillez rรฉessayer.",
+ "connections.clear_all.success.title": "Toutes les connexions supprimรฉes",
+ "connections.clear_all.success.message": "Toutes les connexions ont รฉtรฉ supprimรฉes avec succรจs",
+ "connections.clear_all.error.title": "รchec de la suppression",
+ "connections.clear_all.error.message": "รchec de la suppression de toutes les connexions. Veuillez rรฉessayer.",
+ "connections.errors.clear_all_failed": "รchec de la suppression",
+ "connections.errors.clear_all_failed_message": "รchec de la suppression de toutes les connexions. Veuillez rรฉessayer.",
+ "connections.errors.invalid_master_key": "Clรฉ maรฎtre invalide fournie",
+ "connections.errors.invalid_auth_provider": "Fournisseur d'authentification invalide ou inexistant",
+ "connections.errors.cannot_retrieve_created": "รchec de la rรฉcupรฉration de la connexion crรฉรฉe",
+ "connections.errors.not_found": "Connexion non trouvรฉe",
+ "connections.errors.update_not_implemented": "Mise ร jour de connexion non implรฉmentรฉe",
+ "connections.errors.delete_all_failed": "รchec de la suppression de toutes les connexions",
+ "connections.errors.favorite_not_found": "L'objet SObject n'est pas dans les favoris",
+ "connections.errors.favorite_already_exists": "L'objet SObject est dรฉjร dans les favoris pour cette connexion",
+ "connections.messages.favorite_removed": "Favori supprimรฉ avec succรจs",
+ "connections.renameConnection": "Renommer la connexion",
+ "connections.newName": "Nouveau nom",
+ "connections.enterNewName": "Saisissez le nouveau nom de connexion",
+ "connections.renaming": "Renommage...",
+ "connections.save": "Enregistrer",
+ "connections.rename.success.title": "Connexion renommรฉe",
+ "connections.rename.success.message": "La connexion a รฉtรฉ renommรฉe avec succรจs",
+ "connections.rename.error.title": "รchec du renommage",
+ "connections.rename.error.message": "รchec du renommage de la connexion. Veuillez rรฉessayer.",
+ "connections.rename.error.invalid_name": "Nom invalide",
+ "connections.rename.error.invalid_name_message": "Veuillez fournir un nom de connexion valide",
+ "connections.name": "Nom",
+ "connections.actions": "Actions",
+ "connections.errors.cannot_retrieve_created_details": "รchec de la rรฉcupรฉration de la connexion crรฉรฉe",
+ "connections.errors.cannot_retrieve_updated_details": "รchec de la rรฉcupรฉration de la connexion mise ร jour",
+ "connections.errors.database_operation_failed": "รchec de l'opรฉration de base de donnรฉes",
+ "connections.errors.credential_test_failed": "Identifiants Salesforce invalides",
+ "connections.disconnect.title": "Fermer la connexion",
+ "connections.disconnect.message": "La connexion Salesforce actuelle sera fermรฉe. Cela effacera toutes les informations d'identification dรฉcryptรฉes de la mรฉmoire. Voulez-vous continuer ?",
+ "connections.disconnect.confirm": "Oui, fermer la connexion",
+ "connections.disconnect.cancel": "Annuler",
+ "conversations.count": "{count} conversations",
+ "conversations.agents.datapilot": "DataPilot",
+ "conversations.agents.salesforce": "Salesforce",
+ "database.errors.init_failed": "รchec de l'initialisation de la base de donnรฉes",
+ "database.errors.query_failed": "รchec de l'exรฉcution de la requรชte de base de donnรฉes",
+ "database.errors.status_check_failed": "รchec de la vรฉrification du statut de la base de donnรฉes",
+ "debug.sessionManagement": "Gestion de session",
+ "debug.masterKey.set": "Dรฉfinir",
+ "debug.masterKey.notSet": "Non dรฉfini",
+ "debug.performance": "Performance",
+ "debug.performance.fallback": "Performance",
+ "debug.memoryApiNotAvailable": "API mรฉmoire non disponible",
+ "debug.memoryApiNotAvailable.fallback": "API mรฉmoire non disponible",
+ "debug.recentLogs": "Journaux rรฉcents",
+ "debug.lastLogs": "Derniรจres {count} entrรฉes de journal",
+ "debug.appDebug": "Dรฉbogage de l'application",
+ "debug.debugMode": "Mode dรฉbogage",
+ "details.tab.title": "Dรฉtails de l'objet",
+ "details.tab.subtitle": "Visualiser des informations dรฉtaillรฉes sur les objets Salesforce",
+ "details.tab.search.placeholder": "Rechercher des objets...",
+ "details.tab.filter.placeholder": "Filtrer par type",
+ "details.tab.filter.all": "Tous les objets",
+ "details.tab.filter.standard": "Objets standard",
+ "details.tab.filter.custom": "Objets personnalisรฉs",
+ "details.tab.actions.export": "Exporter les dรฉtails",
+ "details.tab.actions.settings": "Paramรจtres",
+ "details.tab.object.custom": "Personnalisรฉ",
+ "details.tab.object.standard": "Standard",
+ "details.tab.stats.fields": "Champs",
+ "details.tab.stats.relationships": "Relations",
+ "details.tab.stats.triggers": "Dรฉclencheurs",
+ "details.tab.stats.validationRules": "Rรจgles de validation",
+ "details.tab.permissions.title": "Permissions d'objet",
+ "details.tab.permissions.queryable": "Interrogeable",
+ "details.tab.permissions.searchable": "Recherchable",
+ "details.tab.permissions.createable": "Crรฉable",
+ "details.tab.permissions.updateable": "Modifiable",
+ "details.tab.permissions.deletable": "Supprimable",
+ "details.tab.fields.title": "Champs {object}",
+ "details.tab.fields.name": "Nom du champ",
+ "details.tab.fields.label": "รtiquette",
+ "details.tab.fields.type": "Type",
+ "details.tab.fields.length": "Longueur",
+ "details.tab.fields.properties": "Propriรฉtรฉs",
+ "details.tab.field.required": "Obligatoire",
+ "details.tab.field.unique": "Unique",
+ "details.tab.field.externalId": "ID externe",
+ "details.tab.field.calculated": "Calculรฉ",
+ "details.tab.metadata.title": "Informations de mรฉtadonnรฉes",
+ "details.tab.metadata.lastModified": "Derniรจre modification",
+ "details.tab.metadata.createdBy": "Crรฉรฉ par",
+ "details.tab.empty.title": "Sรฉlectionner un objet",
+ "details.tab.empty.description": "Choisissez un objet dans la liste pour voir ses informations dรฉtaillรฉes",
+ "details.tab.objects.title": "Objets ({count})",
+ "details.tab.fields.selectObject": "Sรฉlectionnez un objet pour voir les champs",
+ "error.queryExecution.title": "Erreur d'exรฉcution de requรชte",
+ "error.details": "Dรฉtails de l'erreur",
+ "error.details.fallback": "Dรฉtails de l'erreur",
+ "error.general": "Une erreur s'est produite",
+ "error.connection": "รchec de la connexion",
+ "error.query": "รchec de l'exรฉcution de la requรชte",
+ "error.validation": "Erreur de validation",
+ "error.unknown": "Erreur inconnue",
+ "error.suggestions.title": "Suggestions",
+ "error.notifications.apiError.title": "Erreur API",
+ "error.notifications.apiError.generic": "Une erreur s'est produite lors de la communication avec le serveur",
+ "error.notifications.apiError.authentication": "Erreur d'authentification",
+ "error.notifications.apiError.validation": "Erreur de validation",
+ "error.notifications.apiError.server": "Erreur du serveur",
+ "error.notifications.apiError.connection": "Erreur de connexion",
+ "error.notifications.success.title": "Succรจs",
+ "error.notifications.error.title": "Erreur",
+ "error.notifications.warning.title": "Avertissement",
+ "error.notifications.info.title": "Information",
+ "error.suggestions.syntax": "Vรฉrifiez votre syntaxe SOQL",
+ "error.suggestions.fields": "Vรฉrifiez que les noms de champs existent",
+ "error.suggestions.permissions": "Vรฉrifiez vos permissions",
+ "error.suggestions.typos": "Recherchez les fautes de frappe dans les noms d'objets ou de champs",
+ "error.api.server": "Erreur du serveur",
+ "error.api.request": "Erreur de requรชte",
+ "i18n.errors.translation_not_found": "Traduction non trouvรฉe",
+ "i18n.errors.language_retrieval_failed": "รchec de la rรฉcupรฉration des langues",
+ "i18n.errors.locale_retrieval_failed": "รchec de la rรฉcupรฉration des locales disponibles",
+ "i18n.errors.locale_not_found": "Locale non trouvรฉe",
+ "i18n.errors.translation_retrieval_failed": "รchec de la rรฉcupรฉration des traductions",
+ "i18n.errors.key_retrieval_failed": "รchec de la rรฉcupรฉration de la clรฉ de traduction",
+ "i18n.errors.page_retrieval_failed": "รchec de la rรฉcupรฉration des pages disponibles",
+ "i18n.errors.stats_retrieval_failed": "รchec de la rรฉcupรฉration des statistiques de traduction",
+ "i18n.errors.language_not_found": "Langue non trouvรฉe",
+ "i18n.errors.set_default_failed": "รchec de la dรฉfinition de la langue par dรฉfaut",
+ "i18n.errors.language_code_already_exists": "Le code de langue existe dรฉjร ",
+ "i18n.errors.language_creation_failed": "รchec de la crรฉation de la langue",
+ "i18n.errors.set_active_status_failed": "รchec de la dรฉfinition du statut actif de la langue",
+ "logs.title": "Journaux de l'application",
+ "logs.clear": "Effacer les journaux",
+ "logs.export": "Exporter les journaux",
+ "logs.level": "Niveau de journal",
+ "logs.timestamp": "Horodatage",
+ "logs.message": "Message",
+ "logs.messages.entry_created_successfully": "Entrรฉe de journal frontend crรฉรฉe avec succรจs",
+ "masterKey.title": "Configuration de la clรฉ maรฎtre",
+ "masterKey.subtitle": "Configurez votre clรฉ de chiffrement maรฎtre",
+ "masterKey.description": "Cette clรฉ sera utilisรฉe pour chiffrer et dรฉchiffrer vos identifiants de connexion enregistrรฉs. Gardez-la en sรฉcuritรฉ et souvenez-vous-en bien.",
+ "masterKey.setup.note": "Cette clรฉ est stockรฉe localement comme un hachage SHA-256 avec sel et utilisรฉe pour chiffrer vos identifiants de connexion en utilisant AES-256-GCM avec dรฉrivation de clรฉ PBKDF2. Elle sera requise pour accรฉder ร vos connexions enregistrรฉes.",
+ "masterKey.label": "Clรฉ maรฎtre",
+ "masterKey.input.label": "Clรฉ maรฎtre",
+ "masterKey.input.placeholder": "Saisissez votre clรฉ maรฎtre (minimum 8 caractรจres)",
+ "masterKey.input.description": "Choisissez une clรฉ forte et mรฉmorable. Elle sera utilisรฉe pour chiffrer vos donnรฉes de connexion.",
+ "masterKey.lengthIndicator": "Longueur : {length}/{minLength} caractรจres",
+ "masterKey.validIndicator": "Valide",
+ "masterKey.invalidIndicator": "Invalide",
+ "masterKey.loading": "Chargement...",
+ "masterKey.initializing": "Initialisation de l'application...",
+ "masterKey.setup.title": "Configuration de la clรฉ maรฎtre",
+ "masterKey.setup.subtitle": "Crรฉez une clรฉ maรฎtre pour chiffrer vos donnรฉes sensibles",
+ "masterKey.setup.placeholder": "Saisissez votre clรฉ maรฎtre",
+ "masterKey.setup.button": "Dรฉfinir la clรฉ maรฎtre",
+ "masterKey.validate.button": "Valider la clรฉ maรฎtre",
+ "masterKey.prompt.title": "Saisir la clรฉ maรฎtre",
+ "masterKey.prompt.subtitle": "Veuillez saisir votre clรฉ maรฎtre pour accรฉder aux connexions enregistrรฉes",
+ "masterKey.prompt.placeholder": "Saisissez votre clรฉ maรฎtre",
+ "masterKey.prompt.button": "Dรฉverrouiller",
+ "masterKey.reset.button": "Rรฉinitialiser la clรฉ maรฎtre",
+ "masterKey.error.tooShort": "La clรฉ maรฎtre doit contenir au moins 8 caractรจres",
+ "masterKey.error.invalid": "Clรฉ maรฎtre invalide. Veuillez rรฉessayer.",
+ "masterKey.invalidKey": "Clรฉ maรฎtre invalide. Veuillez rรฉessayer.",
+ "masterKey.success.setup": "Clรฉ maรฎtre dรฉfinie avec succรจs !",
+ "masterKey.success.validate": "Clรฉ maรฎtre validรฉe avec succรจs !",
+ "masterKey.minLengthError": "La clรฉ maรฎtre doit contenir au moins 8 caractรจres",
+ "masterKey.reset.confirm": "รtes-vous sรปr de vouloir rรฉinitialiser votre clรฉ maรฎtre ? Cela supprimera dรฉfinitivement TOUTES les connexions enregistrรฉes, requรชtes enregistrรฉes et historique des requรชtes. Cette action ne peut pas รชtre annulรฉe.",
+ "masterKey.resetError": "รchec de la rรฉinitialisation de la clรฉ maรฎtre. Veuillez rรฉessayer.",
+ "masterKey.failedAttempts": "Tentatives รฉchouรฉes : {count}/{max}",
+ "masterKey.resetAvailable": "Option de rรฉinitialisation disponible aprรจs {max} tentatives รฉchouรฉes",
+ "master_key.error.too_short": "La clรฉ maรฎtre doit contenir au moins 8 caractรจres",
+ "master_key.error.key_mismatch": "Clรฉ maรฎtre invalide - la clรฉ ne correspond pas au hachage stockรฉ",
+ "master_key.error.not_found_to_delete": "Clรฉ maรฎtre non trouvรฉe pour la suppression",
+ "master_key.error.invalid_key_for_deletion": "Clรฉ maรฎtre invalide fournie pour la suppression",
+ "master_key.error.internal_server_error": "Erreur interne du serveur lors du traitement de la clรฉ maรฎtre",
+ "master_key.error.failed_to_create": "รchec de la crรฉation de la nouvelle clรฉ maรฎtre",
+ "master_key.error.invalid_key": "Clรฉ maรฎtre invalide fournie",
+ "master_key.error.failed_to_create_with_reason": "รchec de la crรฉation de la clรฉ maรฎtre : {reason}",
+ "master_key.error.cannot_retrieve_info": "Impossible de rรฉcupรฉrer les informations de la clรฉ maรฎtre",
+ "master_key.error.validation_failed": "รchec de la validation de la clรฉ maรฎtre",
+ "master_key.error.not_found_to_update": "Clรฉ maรฎtre non trouvรฉe pour la mise ร jour",
+ "master_key.error.failed_to_update": "รchec de la mise ร jour de la clรฉ maรฎtre",
+ "master_key.error.invalid_new_key": "Nouvelle clรฉ maรฎtre invalide fournie",
+ "master_key.error.failed_to_delete": "รchec de la suppression de la clรฉ maรฎtre",
+ "master_key.errors.create_failed": "รchec de la crรฉation de la clรฉ maรฎtre",
+ "master_key.errors.invalid_key": "Clรฉ maรฎtre invalide ou faible",
+ "master_key.errors.invalid_key_provided": "Clรฉ maรฎtre invalide fournie",
+ "master_key.errors.key_validation_failed": "รchec de la validation de la clรฉ",
+ "master_key.errors.key_mismatch": "Clรฉ maรฎtre non correspondante",
+ "master_key.errors.key_does_not_match": "La clรฉ fournie ne correspond pas ร la clรฉ existante",
+ "master_key.errors.key_too_short": "Clรฉ maรฎtre trop courte",
+ "master_key.errors.min_length_required": "La clรฉ maรฎtre doit contenir au moins 8 caractรจres",
+ "master_key.errors.cannot_retrieve_info": "Impossible de rรฉcupรฉrer les informations de la clรฉ maรฎtre",
+ "master_key.errors.validation_failed": "รchec de la validation de la clรฉ maรฎtre",
+ "master_key.errors.not_found": "Clรฉ maรฎtre non trouvรฉe",
+ "master_key.errors.update_failed": "รchec de la mise ร jour de la clรฉ maรฎtre",
+ "master_key.errors.invalid_or_weak_key": "Clรฉ maรฎtre invalide ou faible",
+ "master_key.errors.invalid_new_key": "Nouvelle clรฉ maรฎtre invalide",
+ "master_key.errors.delete_failed": "รchec de la suppression de la clรฉ maรฎtre",
+ "master_key.errors.invalid_key_for_deletion": "Clรฉ maรฎtre invalide pour la suppression",
+ "master_key.messages.deleted_permanently": "Clรฉ maรฎtre et toutes les connexions supprimรฉes dรฉfinitivement",
+ "query.pleaseEnterQuery": "Veuillez saisir une requรชte SOQL ร exรฉcuter",
+ "query.pleaseEnterQueryMessage": "Veuillez saisir une requรชte SOQL avant l'exรฉcution.",
+ "query.executionFailed": "รchec de l'exรฉcution de la requรชte. Veuillez vรฉrifier votre syntaxe SOQL et rรฉessayer.",
+ "query.loadMoreFailed": "รchec du chargement de plus de rรฉsultats. Veuillez rรฉessayer.",
+ "query.recordUpdateFailed": "รchec de la mise ร jour de l'enregistrement. Veuillez rรฉessayer.",
+ "query.executing": "Exรฉcution de la requรชte Salesforce",
+ "query.placeholder": "Saisissez votre requรชte SOQL ici...",
+ "query.editor.label": "Requรชte",
+ "query.editor.placeholder": "Saisissez votre requรชte SOQL ici...",
+ "query.editor.execute": "Exรฉcuter la requรชte",
+ "query.editor.undo": "Annuler",
+ "query.editor.redo": "Rรฉtablir",
+ "query.editor.quickLoad.placeholder": "Charger une requรชte enregistrรฉe",
+ "query.editor.quickLoad.loading": "Chargement...",
+ "query.editor.quickLoad.noQueries": "Aucune requรชte enregistrรฉe",
+ "query.notifications.executed.title": "Requรชte exรฉcutรฉe",
+ "query.notifications.executed.message": "Requรชte exรฉcutรฉe avec succรจs",
+ "query.notifications.error.title": "Erreur de requรชte",
+ "query.notifications.error.message": "Une erreur s'est produite lors de l'exรฉcution de la requรชte",
+ "query.editor.executeAll": "Exรฉcuter toute la requรชte",
+ "query.editor.saveQuery": "Enregistrer la requรชte",
+ "query.editor.deleteChecked": "Supprimer la sรฉlection",
+ "query.editor.saveModal.title": "Sauvegarder la requรชte",
+ "query.editor.saveModal.name.label": "Nom de la requรชte",
+ "query.editor.saveModal.name.placeholder": "Entrer le nom de la requรชte...",
+ "query.editor.saveModal.name.description": "Choisissez un nom descriptif pour votre requรชte",
+ "query.editor.saveModal.save": "Sauvegarder la requรชte",
+ "query.editor.untitled": "Requรชte sans titre",
+ "query.editor.dropField": "Glissez le champ ici pour l'ajouter ร la requรชte",
+ "query.editor.tabs.query": "รditeur de requรชtes",
+ "query.editor.tabs.savedQueries": "Requรชtes enregistrรฉes",
+ "query.editor.tabs.schema": "Graphique SObjects",
+ "query.editor.tabs.apex": "Apex",
+ "query.editor.tabs.details": "Dรฉtails",
+ "query.editor.tabs.aiAssistant": "Assistant IA",
+ "query.editor.save.success.title": "Requรชte enregistrรฉe",
+ "query.editor.save.success.message": "{name} a รฉtรฉ enregistrรฉe avec succรจs",
+ "query.editor.save.error.title": "รchec de l'enregistrement",
+ "query.editor.save.error.invalid": "Veuillez fournir un nom de requรชte valide",
+ "query.editor.save.error.no_connection": "Aucune connexion active. Veuillez vous connecter ร Salesforce d'abord.",
+ "query.editor.save.error.message": "รchec de l'enregistrement de la requรชte. Veuillez rรฉessayer.",
+ "query.executedSuccessfully": "Requรชte Salesforce exรฉcutรฉe avec succรจs",
+ "query.format.success.title": "Requรชte formatรฉe",
+ "query.format.success.message": "Votre requรชte SOQL a รฉtรฉ formatรฉe avec succรจs",
+ "query.format.already.title": "Requรชte dรฉjร formatรฉe",
+ "query.format.already.message": "Votre requรชte SOQL est dรฉjร correctement formatรฉe",
+ "query.format.error.title": "รchec du formatage",
+ "query.format.error.message": "รchec du formatage de la requรชte SOQL. Veuillez vรฉrifier la syntaxe.",
+ "query.maxRecordsReached": "Impossible de charger plus de {maxRecords} enregistrements. Veuillez affiner votre requรชte pour obtenir des rรฉsultats plus spรฉcifiques.",
+ "query.approachingMaxRecords": "Approche de la limite d'enregistrements maximum ({current}/{max}). Considรฉrez l'affinement de votre requรชte pour de meilleures performances.",
+ "query.maxRecordsReachedMessage": "Ceci est l'ensemble de rรฉsultats maximum configurรฉ ({maxRecords} enregistrements) qui peut รชtre retournรฉ.",
+ "query.warning": "Avertissement",
+ "query.editor.format": "Formater",
+ "query.editor.clear": "Effacer",
+ "query.editor.import": "Importer",
+ "query.editor.import.success.title": "Requรชte importรฉe",
+ "query.editor.import.success.message": "Requรชte importรฉe avec succรจs depuis \"{fileName}\"",
+ "query.editor.import.error.title": "รchec de l'importation",
+ "query.editor.import.error.message": "รchec de la lecture du fichier sรฉlectionnรฉ",
+ "query.editor.clearModal.title": "Effacer la requรชte",
+ "query.editor.clearModal.message": "รtes-vous sรปr de vouloir effacer la requรชte ?",
+ "query.editor.clearModal.warning": "Cette action ne peut pas รชtre annulรฉe.",
+ "query.editor.clearModal.confirm": "Effacer la requรชte",
+ "query.dragDrop.parseError.title": "Erreur d'analyse de requรชte",
+ "query.dragDrop.parseError.message": "รchec de l'analyse de la requรชte actuelle. Veuillez vรฉrifier la syntaxe.",
+ "query.dragDrop.sobjectNotFound.title": "SObject non trouvรฉ",
+ "query.dragDrop.sobjectNotFound.message": "SObject \"{sobjectName}\" non trouvรฉ dans la requรชte actuelle. SObjects disponibles : {availableSObjects}",
+ "query.dragDrop.fieldExists.title": "Champ dรฉjร existant",
+ "query.dragDrop.fieldExists.message": "Le champ \"{fieldName}\" existe dรฉjร dans SObject \"{sobjectName}\". Aucun changement effectuรฉ.",
+ "query.dragDrop.fieldAdded.title": "Champ ajoutรฉ avec succรจs",
+ "query.dragDrop.fieldAdded.message": "Le champ \"{fieldName}\" a รฉtรฉ ajoutรฉ ร SObject \"{sobjectName}\" dans la requรชte.",
+ "query.dragDrop.insertionError.title": "Erreur d'insertion de champ",
+ "query.dragDrop.insertionError.message": "รchec de l'insertion du champ \"{fieldName}\" dans SObject \"{sobjectName}\" en utilisant la mรฉthode AST. Retour ร la manipulation de chaรฎne.",
+ "query.dragDrop.newQueryCreated.title": "Nouvelle requรชte crรฉรฉe",
+ "query.dragDrop.newQueryCreated.message": "Nouvelle requรชte crรฉรฉe avec le champ \"{fieldName}\" de SObject \"{sobjectName}\".",
+ "query.dragDrop.fallbackMethod.title": "Utilisation de la mรฉthode de secours",
+ "query.dragDrop.fallbackMethod.message": "L'insertion basรฉe sur AST a รฉchouรฉ pour le champ \"{fieldName}\" dans SObject \"{sobjectName}\". Utilisation de la mรฉthode de manipulation de chaรฎne.",
+ "query.dragDrop.newQueryCreatedFallback.title": "Nouvelle requรชte crรฉรฉe (Secours)",
+ "query.dragDrop.newQueryCreatedFallback.message": "Nouvelle requรชte crรฉรฉe avec le champ \"{fieldName}\" de SObject \"{sobjectName}\" en utilisant la mรฉthode de secours.",
+ "query.dragDrop.differentSObject.title": "SObject diffรฉrent dรฉtectรฉ",
+ "query.dragDrop.differentSObject.message": "Nouvelle requรชte crรฉรฉe pour SObject \"{sobjectName}\" avec le champ \"{fieldName}\" (diffรฉrent du SObject de la requรชte actuelle).",
+ "query.dragDrop.fieldExistsFallback.title": "Champ dรฉjร existant (Secours)",
+ "query.dragDrop.fieldExistsFallback.message": "Le champ \"{fieldName}\" existe dรฉjร dans SObject \"{sobjectName}\". Aucun changement effectuรฉ.",
+ "query.dragDrop.fieldAddedFallback.title": "Champ ajoutรฉ (Secours)",
+ "query.dragDrop.fieldAddedFallback.message": "Le champ \"{fieldName}\" a รฉtรฉ ajoutรฉ ร SObject \"{sobjectName}\" en utilisant la mรฉthode de secours.",
+ "query.dragDrop.newQuery.title": "Nouvelle requรชte crรฉรฉe",
+ "query.dragDrop.newQuery.message": "Nouvelle requรชte crรฉรฉe pour SObject \"{sobjectName}\" car l'รฉditeur de requรชtes รฉtait vide.",
+ "query.dragDrop.defaultQuery.title": "Nouvelle requรชte crรฉรฉe",
+ "query.dragDrop.defaultQuery.message": "Nouvelle requรชte crรฉรฉe pour SObject \"{sobjectName}\" car aucune relation n'a รฉtรฉ dรฉtectรฉe avec la requรชte actuelle.",
+ "query.dragDrop.subqueryCreated.title": "Sous-requรชte crรฉรฉe",
+ "query.dragDrop.subqueryCreated.message": "Champ \"{fieldName}\" ajoutรฉ depuis SObject \"{sobjectName}\" comme sous-requรชte en utilisant la relation \"{relationshipName}\".",
+ "query.editor.astToGraph": "Requรชte vers Graphique",
+ "query.editor.astToGraph.success.title": "Graphique gรฉnรฉrรฉ",
+ "query.editor.astToGraph.success.message": "Requรชte convertie avec succรจs en visualisation graphique",
+ "query.editor.astToGraph.error.title": "Aucune requรชte",
+ "query.editor.astToGraph.error.noQuery": "Veuillez saisir une requรชte ร convertir en graphique",
+ "query.editor.astToGraph.error.invalidQuery": "Impossible d'analyser la requรชte. Veuillez vรฉrifier la syntaxe.",
+ "query.editor.astToGraph.error.conversionFailed": "รchec de la conversion de la requรชte en graphique. Veuillez rรฉessayer.",
+ "query.editor.autoSaving": "Auto-sauvegarde...",
+ "query.editor.autoSave.success": "Requรชte auto-sauvegardรฉe avec succรจs",
+ "query.editor.autoSave.failed": "รchec de l'auto-sauvegarde",
+ "query.editor.autoSave.toggle.label": "Auto-sauvegarde",
+ "query.editor.autoSave.toggle.enabled": "Auto-sauvegarde activรฉe - les requรชtes seront sauvegardรฉes aprรจs exรฉcution",
+ "query.editor.autoSave.toggle.disabled": "Auto-sauvegarde dรฉsactivรฉe - cliquez pour activer",
+ "query.editor.saveModal.operation.label": "Opรฉration",
+ "query.editor.saveModal.operation.create": "Crรฉer une nouvelle requรชte",
+ "query.editor.saveModal.operation.update": "Mettre ร jour une requรชte existante",
+ "query.editor.saveModal.selectQuery.label": "Sรฉlectionner la requรชte ร mettre ร jour",
+ "query.editor.saveModal.selectQuery.placeholder": "Choisir une requรชte sauvegardรฉe...",
+ "query.editor.saveModal.loading": "Chargement des requรชtes sauvegardรฉes...",
+ "query.editor.updateModal.title": "Mettre ร jour la requรชte",
+ "query.editor.updateModal.name.label": "Nouveau nom de la requรชte",
+ "query.editor.updateModal.name.placeholder": "Entrer le nouveau nom...",
+ "query.editor.updateModal.name.description": "Entrez le nouveau nom pour la requรชte sรฉlectionnรฉe",
+ "query.editor.updateModal.update": "Mettre ร jour la requรชte",
+ "query.editor.update.success.title": "Requรชte mise ร jour",
+ "query.editor.update.success.message": "\"{name}\" a รฉtรฉ mise ร jour avec succรจs",
+ "query.editor.update.error.title": "รchec de la mise ร jour",
+ "query.editor.update.error.message": "รchec de la mise ร jour de la requรชte. Veuillez rรฉessayer.",
+ "query.editor.save.error.no_query_selected": "Veuillez sรฉlectionner une requรชte ร mettre ร jour",
+ "results.columnWidthsReset": "Largeurs des colonnes rรฉinitialisรฉes",
+ "results.columnWidthsResetMessage": "Les largeurs des colonnes ont รฉtรฉ rรฉinitialisรฉes aux valeurs optimales par dรฉfaut",
+ "results.columnWidthsResetError": "รchec de la rรฉinitialisation",
+ "results.columnWidthsResetErrorMessage": "รchec de la rรฉinitialisation des largeurs des colonnes",
+ "results.resetColumns": "Rรฉinitialiser les colonnes",
+ "results.resetColumnWidthsTooltip": "Rรฉinitialiser les largeurs des colonnes aux valeurs optimales par dรฉfaut",
+ "results.loading": "Chargement...",
+ "results.rightClickForDetails": "Clic droit sur cette ligne pour voir les options de dรฉtail disponibles",
+ "results.closeDetailTable": "Fermer ce tableau de dรฉtail",
+ "results.noDetailRelationships": "Aucune relation de dรฉtail dรฉveloppรฉe",
+ "results.backToMasterTable": "Retour au tableau principal",
+ "results.detailFieldsExpanded": "{count} champ{plural} de dรฉtail dรฉveloppรฉ{plural}",
+ "results.noId": "Pas d'ID",
+ "results.unknown": "Inconnu",
+ "results.closedWon": "Gagnรฉ fermรฉ",
+ "results.closedLost": "Perdu fermรฉ",
+ "results.open": "Ouvert",
+ "results.closed": "Fermรฉ",
+ "results.high": "รlevรฉ",
+ "results.hot": "Chaud",
+ "results.object": "Objet",
+ "results.name": "Nom",
+ "results.id": "Id",
+ "results.developerName": "NomDรฉveloppeur",
+ "results.label": "รtiquette",
+ "results.type": "Type",
+ "results.value": "Valeur",
+ "results.text": "Texte",
+ "results.goToPreviousPage": "Aller ร la page prรฉcรฉdente",
+ "results.goToNextPage": "Aller ร la page suivante",
+ "results.updateRecordFailed": "รchec de la mise ร jour de l'enregistrement. Veuillez rรฉessayer.",
+ "results.doubleClickToEditTitle": "Double-cliquez pour modifier",
+ "results.title": "Rรฉsultats de la requรชte",
+ "results.moreRecordsAvailable": "Plus d'enregistrements disponibles",
+ "results.editModeEnabled": "Mode รฉdition activรฉ",
+ "results.noRecordsFound": "Aucun enregistrement trouvรฉ",
+ "results.noRecordsFoundSubtitle": "Aucun enregistrement n'a รฉtรฉ retournรฉ par votre requรชte",
+ "results.noQueryResults": "Aucun rรฉsultat de requรชte",
+ "results.noQueryResultsSubtitle": "Exรฉcutez une requรชte pour voir les rรฉsultats ici",
+ "results.recordsFoundOne": "1 enregistrement trouvรฉ",
+ "results.recordsFoundMany": "{count} enregistrements trouvรฉs",
+ "results.showingRecords": "{current} sur {total} enregistrements",
+ "results.loadMoreRecords": "Charger plus d'enregistrements",
+ "results.doubleClickToEdit": "Double-cliquez pour modifier",
+ "results.updating": "Mise ร jour...",
+ "results.details.label": "Dรฉtails",
+ "results.export.success.title": "Exportation rรฉussie",
+ "results.export.success.message": "Donnรฉes exportรฉes en {format} avec {count} enregistrements",
+ "results.export.failed.title": "รchec de l'exportation",
+ "results.export.failed.message": "รchec de l'exportation des donnรฉes. Veuillez rรฉessayer.",
+ "results.update.failed.title": "รchec de la mise ร jour",
+ "results.detail.close": "Fermer ce tableau de dรฉtails",
+ "results.maxReached": "Maximum atteint",
+ "results.loadMore": "Charger plus",
+ "results.export.noData.title": "Aucune donnรฉe ร exporter",
+ "results.export.noData.message": "Il n'y a aucun enregistrement ร exporter",
+ "resultsViewPage.field.null": "null",
+ "resultsViewPage.noDetailRecords": "Aucun enregistrement de dรฉtail trouvรฉ",
+ "resultsViewPage.title": "Rรฉsultats de la requรชte",
+ "resultsViewPage.badge.records": "{count} enregistrements",
+ "resultsViewPage.badge.noResults": "Aucun rรฉsultat",
+ "resultsViewPage.resetColumns": "Rรฉinitialiser les colonnes",
+ "resultsViewPage.noRecords": "Aucun enregistrement trouvรฉ",
+ "resultsViewPage.pagination.recordsPerPage": "Enregistrements par page",
+ "resultsViewPage.pagination.text": "{from} - {to} sur {totalRecords} enregistrements",
+ "resultsViewPage.noRecordsAvailable": "Aucun enregistrement disponible",
+ "resultsViewPage.loadingColumns": "Chargement des colonnes...",
+ "resultsViewPage.edit.title": "Modifier l'enregistrement",
+ "resultsViewPage.edit.field": "Champ",
+ "resultsViewPage.edit.value": "Valeur",
+ "resultsViewPage.showDetails": "Afficher les dรฉtails",
+ "resultsViewPage.hideDetails": "Masquer les dรฉtails",
+ "resultsViewPage.showMasterHideDetails": "Afficher le maรฎtre (masquer les dรฉtails)",
+ "resultsViewPage.executingQuery": "Exรฉcution de la requรชte...",
+ "resultsViewPage.pleaseWait": "Veuillez patienter pendant que nous rรฉcupรฉrons vos rรฉsultats",
+ "resultsViewPage.previousResults": "La requรชte prรฉcรฉdente a retournรฉ {count} enregistrements",
+ "salesforce.errors.invalid_connection_params": "Paramรจtres de connexion invalides",
+ "salesforce.errors.connection_failed": "รchec de l'รฉtablissement de la connexion Salesforce",
+ "salesforce.errors.no_connection": "Aucune connexion Salesforce active disponible",
+ "salesforce.errors.invalid_request_params": "Paramรจtres de requรชte invalides",
+ "salesforce.errors.sobject_list_failed": "รchec de la rรฉcupรฉration de la liste SObject depuis Salesforce",
+ "salesforce.errors.invalid_sobject_params": "Nom SObject ou paramรจtres invalides",
+ "salesforce.errors.sobject_not_found": "SObject '{resource_id}' non trouvรฉ ou non accessible",
+ "salesforce.errors.invalid_query": "Requรชte SOQL invalide",
+ "salesforce.errors.query_execution_failed": "รchec de l'exรฉcution de la requรชte SOQL",
+ "salesforce.errors.invalid_next_records_id": "Paramรจtre nextRecordsId invalide",
+ "salesforce.errors.execute_more_failed": "รchec de l'exรฉcution de plus d'enregistrements depuis la requรชte prรฉcรฉdente",
+ "salesforce.errors.invalid_user_request": "Requรชte invalide pour les informations utilisateur",
+ "salesforce.errors.user_info_failed": "รchec de la rรฉcupรฉration des informations utilisateur depuis Salesforce",
+ "salesforce.errors.invalid_usage_request": "Requรชte invalide pour l'utilisation API",
+ "salesforce.errors.usage_failed": "รchec de la rรฉcupรฉration de l'utilisation API depuis Salesforce",
+ "salesforce.errors.status_check_failed": "รchec de la vรฉrification du statut de connexion Salesforce",
+ "salesforce.errors.config_failed": "รchec de la rรฉcupรฉration de la configuration API Salesforce",
+ "salesforce.errors.invalid_record_params": "Paramรจtres de mise ร jour d'enregistrement invalides",
+ "salesforce.errors.record_update_failed": "รchec de la mise ร jour de l'enregistrement",
+ "salesforce.errors.logout_failed": "รchec de la dรฉconnexion depuis Salesforce",
+ "saved_apex.title": "Code Apex enregistrรฉ",
+ "saved_apex.subtitle": "Gรฉrez votre code Apex enregistrรฉ avec les niveaux de dรฉbogage",
+ "saved_apex.search.placeholder": "Rechercher du code Apex enregistrรฉ...",
+ "saved_apex.filter.status.placeholder": "Filtrer par statut",
+ "saved_apex.filter.status.all": "Tous les statuts",
+ "saved_apex.filter.status.favorite": "Favoris",
+ "saved_apex.filter.status.recent": "Rรฉcent",
+ "saved_apex.filter.type.placeholder": "Filtrer par type",
+ "saved_apex.filter.type.all": "Tous les types",
+ "saved_apex.filter.type.anonymous": "Anonyme",
+ "saved_apex.filter.type.class": "Classe",
+ "saved_apex.filter.type.trigger": "Dรฉclencheur",
+ "saved_apex.filter.type.interface": "Interface",
+ "saved_apex.filter.type.enum": "รnumรฉration",
+ "saved_apex.filter.type.test_class": "Classe de test",
+ "saved_apex.actions.create": "Crรฉer nouveau",
+ "saved_apex.actions.execute": "Exรฉcuter",
+ "saved_apex.actions.edit": "Modifier",
+ "saved_apex.actions.delete": "Supprimer",
+ "saved_apex.actions.toggle_favorite": "Basculer favori",
+ "saved_apex.tabs.all": "Tous ({count})",
+ "saved_apex.tabs.favorites": "Favoris ({count})",
+ "saved_apex.tabs.recent": "Rรฉcent ({count})",
+ "saved_apex.code.modified": "Modifiรฉ : {date}",
+ "saved_apex.code.executions": "{count} exรฉcutions",
+ "saved_apex.code.last_executed": "Derniรจre exรฉcution : {date}",
+ "saved_apex.code.status.success": "Succรจs",
+ "saved_apex.code.status.error": "Erreur",
+ "saved_apex.code.status.pending": "En attente",
+ "saved_apex.error.not_found": "Code Apex enregistrรฉ non trouvรฉ",
+ "saved_apex.error.validation": "Erreur de validation : {error}",
+ "saved_apex.error.internal": "Erreur interne du serveur : {error}",
+ "saved_apex.error.execution_failed": "รchec de l'exรฉcution du code Apex",
+ "saved_apex.success.created": "Code Apex enregistrรฉ crรฉรฉ avec succรจs",
+ "saved_apex.success.updated": "Code Apex enregistrรฉ mis ร jour avec succรจs",
+ "saved_apex.success.deleted": "Code Apex enregistrรฉ supprimรฉ avec succรจs",
+ "saved_apex.success.executed": "Code Apex enregistrรฉ exรฉcutรฉ avec succรจs",
+ "saved_apex.success.favorite_toggled": "Statut favori basculรฉ avec succรจs",
+ "saved_apex.error.connection_required": "L'UUID de connexion est requis pour cette opรฉration",
+ "saved_apex.error.invalid_connection": "UUID de connexion invalide fourni",
+ "saved_apex.error.connection_validation_failed": "รchec de la validation de l'UUID de connexion",
+ "saved_apex.error.debug_levels_invalid": "Configuration des niveaux de dรฉbogage invalide",
+ "saved_apex.error.code_type_invalid": "Type de code Apex invalide",
+ "saved_apex.error.name_required": "Le nom du code Apex est requis",
+ "saved_apex.error.code_required": "Le contenu du code Apex est requis",
+ "saved_apex.error.duplicate_name": "Un code Apex avec ce nom existe dรฉjร ",
+ "saved_apex.error.execution_timeout": "L'exรฉcution du code Apex a expirรฉ",
+ "saved_apex.error.compilation_failed": "รchec de la compilation du code Apex",
+ "saved_apex.error.runtime_error": "Erreur d'exรฉcution du code Apex",
+ "saved_apex.messages.deleted_successfully": "Code Apex enregistrรฉ supprimรฉ avec succรจs",
+ "saved_queries.title": "Requรชtes enregistrรฉes",
+ "saved_queries.no_connection": "Aucune connexion",
+ "saved_queries.no_connection_message": "Veuillez sรฉlectionner une connexion pour voir les requรชtes enregistrรฉes",
+ "saved_queries.empty": "Aucune requรชte enregistrรฉe",
+ "saved_queries.empty_message": "Aucune requรชte enregistrรฉe trouvรฉe pour cette connexion. Crรฉez votre premiรจre requรชte enregistrรฉe !",
+ "saved_queries.refresh": "Actualiser",
+ "saved_queries.executions": "exรฉcutions",
+ "saved_queries.success.copied": "Requรชte copiรฉe",
+ "saved_queries.success.copied_message": "{name} copiรฉ dans le presse-papiers",
+ "saved_queries.success.deleted": "Requรชte supprimรฉe",
+ "saved_queries.success.deleted_message": "{name} a รฉtรฉ supprimรฉe avec succรจs",
+ "saved_queries.success.loaded": "Requรชte chargรฉe",
+ "saved_queries.success.loaded_message": "{name} chargรฉe avec succรจs",
+ "saved_queries.errors.load_failed": "รchec du chargement des requรชtes enregistrรฉes",
+ "saved_queries.errors.load_failed_message": "รchec du chargement des requรชtes enregistrรฉes. Veuillez rรฉessayer.",
+ "saved_queries.error.load_failed": "รchec du chargement",
+ "saved_queries.error.load_failed_message": "รchec du chargement des requรชtes enregistrรฉes",
+ "saved_queries.errors.invalid_data": "Donnรฉes de requรชte enregistrรฉe invalides",
+ "saved_queries.errors.query_required": "Le texte de la requรชte est obligatoire",
+ "saved_queries.errors.invalid_filter": "Paramรจtres de filtre invalides",
+ "saved_queries.errors.filter_invalid": "รchec de la validation du filtre",
+ "saved_queries.errors.not_found": "Requรชte enregistrรฉe non trouvรฉe",
+ "saved_queries.errors.invalid_identifier": "Identifiant de requรชte enregistrรฉe invalide",
+ "saved_queries.errors.uuid_invalid": "รchec de la validation UUID",
+ "saved_queries.errors.invalid_update_data": "Donnรฉes de mise ร jour invalides",
+ "saved_queries.errors.update_failed": "รchec de l'opรฉration de mise ร jour",
+ "saved_queries.errors.invalid_identifier_for_deletion": "Identifiant invalide pour la suppression",
+ "saved_queries.messages.execution_incremented": "Compteur d'exรฉcution incrรฉmentรฉ avec succรจs",
+ "saved_queries.error.delete_failed": "รchec de la suppression",
+ "saved_queries.error.delete_failed_message": "รchec de la suppression de la requรชte",
+ "saved_queries.error.duplicate_name": "Une requรชte avec ce nom existe dรฉjร ",
+ "saved_queries.actions.load_to_editor": "Charger dans l'รฉditeur",
+ "saved_queries.actions.show_query": "Afficher la requรชte",
+ "saved_queries.actions.hide_query": "Masquer la requรชte",
+ "saved_queries.favorite.added": "Ajoutรฉ aux favoris",
+ "saved_queries.favorite.removed": "Supprimรฉ des favoris",
+ "saved_queries.favorite.added_message": "{name} ajoutรฉ aux favoris",
+ "saved_queries.favorite.removed_message": "{name} supprimรฉ des favoris",
+ "saved_query.error.duplicate_name": "Une requรชte avec ce nom existe dรฉjร ",
+ "saved_query.error.invalid_name": "Nom de requรชte invalide",
+ "saved_query.error.invalid_query_text": "Le texte de la requรชte est requis",
+ "saved_query.error.invalid_connection": "Connexion manquante ou invalide",
+ "saved_query.error.not_found": "Requรชte enregistrรฉe introuvable",
+ "saved_queries.success.renamed": "Requรชte renommรฉe",
+ "saved_queries.success.renamed_message": "{name} renommรฉe avec succรจs",
+ "saved_queries.error.rename_failed": "รchec du renommage",
+ "saved_queries.error.rename_failed_message": "รchec du renommage de la requรชte",
+ "saved_queries.error.invalid_name": "Nom invalide",
+ "saved_queries.error.invalid_name_message": "Le nom de la requรชte ne peut pas รชtre vide",
+ "schema.tooltip.collapse": "Cliquez pour rรฉduire {name}",
+ "schema.tooltip.expand": "Cliquez pour dรฉvelopper {name}",
+ "schema.tooltip.dragField": "Glissez {name} pour ajouter ร la requรชte",
+ "schema.custom": "Personnalisรฉ",
+ "schema.notConnected": "Non connectรฉ",
+ "schema.connectToView": "Connectez-vous pour voir l'arbre SObjects",
+ "schema.searchPlaceholder": "Rechercher SObjects...",
+ "schema.all": "Tout",
+ "schema.standard": "Standard",
+ "schema.notifications.loaded.title": "Arbre SObjects chargรฉ",
+ "schema.notifications.loaded.message": "L'arbre SObjects a รฉtรฉ chargรฉ avec succรจs",
+ "schema.notifications.exportSuccess.title": "Exportation rรฉussie",
+ "schema.notifications.exportSuccess.message": "L'arbre SObjects a รฉtรฉ exportรฉ avec succรจs",
+ "schema.notifications.exportError.title": "Erreur d'exportation",
+ "schema.notifications.exportError.message": "รchec de l'exportation de l'arbre SObjects en PDF. Veuillez rรฉessayer.",
+ "schema.favorites": "Favoris",
+ "schema.noData": "Aucune donnรฉe d'arbre SObjects disponible",
+ "schema.noResults": "Aucun rรฉsultat trouvรฉ",
+ "schema.cacheStatus": "Cache : {current}/{max}",
+ "schema.cacheStatus.fallback": "Cache : {current}/{max} objets",
+ "schema.picklistValues": "Valeurs de liste de sรฉlection",
+ "schema.addToFavorites": "Ajouter aux favoris",
+ "schema.removeFromFavorites": "Supprimer des favoris",
+ "schema.togglingFavorite": "Basculer le favori...",
+ "schema.graph.title": "Graphique SObjects",
+ "schema.graph.subtitle": "Glissez les SObjects de l'arbre pour construire des graphiques de relations interactifs avec DataPilot IA, gรฉnรฉrer des rapports dรฉtaillรฉs de schรฉma et de relations, exporter des diagrammes en PDF/Image, convertir des graphiques en requรชtes SOQL, et analyser des structures SObject complexes avec zoom, panoramique et capacitรฉs de filtrage",
+ "schema.connectionNote": "Note: Assurez-vous que vous avez une connexion Salesforce active pour charger les dรฉtails des SObjects",
+ "schema.loadingSObject": "Chargement SObject...",
+ "schema.selectMasterParent": "Sรฉlectionner le parent maรฎtre",
+ "schema.addChildObjects": "Ajouter des objets enfants",
+ "schema.childObjects": "Objets enfants",
+ "schema.loadingRelationships": "Chargement des relations...",
+ "schema.availableCount": "{count} disponible",
+ "schema.deleteNode": "Supprimer le nลud",
+ "schema.references": "Rรฉfรฉrences",
+ "schema.chooseMasterParentFor": "Choisir le parent maรฎtre pour {nodeName}",
+ "schema.sobjectAddedWithFields": "{sobjectName} ajoutรฉ avec les premiers {initialCount} champs ({hiddenCount} autres champs disponibles mais non affichรฉs)",
+ "schema.contextMenu.addMoreFields": "Ajouter plus de champs",
+ "schema.contextMenu.selectSpecificFields": "Sรฉlectionner des champs spรฉcifiques",
+ "schema.fieldSelection.title": "Sรฉlectionner les champs ร ajouter",
+ "schema.fieldSelection.subtitle": "Sรฉlectionnez les champs ร ajouter ร {sobjectName}",
+ "schema.fieldSelection.searchPlaceholder": "๐ Rechercher des champs par nom, รฉtiquette ou type...",
+ "schema.fieldSelection.selectAll": "Tout sรฉlectionner",
+ "schema.fieldSelection.selectNone": "Tout dรฉsรฉlectionner",
+ "schema.fieldSelection.selectedCount": "{selected} sur {total} champs sรฉlectionnรฉs",
+ "schema.fieldSelection.addSelected": "Ajouter {count} champs sรฉlectionnรฉs",
+ "schema.notifications.noSObjectData.title": "Aucune donnรฉe SObject",
+ "schema.notifications.noSObjectData.message": "Aucune mรฉtadonnรฉe trouvรฉe pour {sobjectName}",
+ "schema.notifications.fieldSelectionError.title": "Erreur de sรฉlection de champ",
+ "schema.notifications.fieldSelectionError.message": "รchec du chargement des champs pour la sรฉlection. Veuillez rรฉessayer.",
+ "schema.notifications.selectedFieldsAdded.title": "Champs sรฉlectionnรฉs ajoutรฉs",
+ "schema.notifications.selectedFieldsAdded.message": "{addedFields} champs sรฉlectionnรฉs ajoutรฉs ร {sobjectName}",
+ "schema.notifications.addSelectedFieldsError.title": "Erreur d'ajout de champs",
+ "schema.notifications.addSelectedFieldsError.message": "รchec de l'ajout des champs sรฉlectionnรฉs. Veuillez rรฉessayer.",
+ "schema.graph.empty.title": "Aucun SObject dans le graphique SObjects",
+ "schema.graph.empty.subtitle": "Glissez les SObjects de l'arbre pour commencer ร construire votre graphique de relations",
+ "schema.export.image": "Exporter en image",
+ "schema.export.pdf": "Exporter en PDF",
+ "schema.export.exporting": "Exportation...",
+ "schema.report.export": "Exporter le rapport",
+ "schema.report.export.pdf": "Exporter en PDF",
+ "schema.graphToQuery": "Convertir le graphique en requรชte",
+ "schema.graphToQuerySuccess": "Graphique converti avec succรจs",
+ "schema.graphToQuerySuccessMessage": "Graphique converti en requรชte SOQL avec {sobject} ({fields} champs, {subqueries} sous-requรชtes)",
+ "schema.graphToQueryError": "รchec de la conversion du graphique",
+ "schema.graphToQueryErrorMessage": "รchec de la conversion du graphique en requรชte SOQL. Veuillez rรฉessayer.",
+ "schema.multipleParentsResolved": "Relations parentes multiples rรฉsolues",
+ "schema.multipleParentsResolvedMessage": "{count} nลuds avaient des relations parentes multiples. Le parent le plus rรฉcent a รฉtรฉ sรฉlectionnรฉ pour chacun : {nodes}",
+ "schema.masterParentSelected": "Parent maรฎtre sรฉlectionnรฉ",
+ "schema.masterParentSelectedMessage": "{masterParent} sรฉlectionnรฉ comme parent maรฎtre pour {childNode}",
+ "schema.notifications.sobjectAdded.title": "SObject ajoutรฉ au canevas",
+ "schema.notifications.sobjectAdded.message": "{sobjectName} ajoutรฉ avec les premiers {displayedFields} champs ({hiddenFields} autres champs disponibles mais non affichรฉs)",
+ "schema.notifications.fieldsAdded.title": "Champs ajoutรฉs",
+ "schema.notifications.fieldsAdded.message": "{sobjectName} : {addedFields} champs supplรฉmentaires ajoutรฉs ({remainingFields} restants)",
+ "schema.notifications.allFieldsAdded.title": "Tous les champs ajoutรฉs",
+ "schema.notifications.allFieldsAdded.message": "{sobjectName} a dรฉjร tous les {totalFields} champs affichรฉs",
+ "schema.notifications.fieldsAddError.title": "Erreur d'ajout de champs",
+ "schema.notifications.fieldsAddError.message": "รchec de l'ajout de plus de champs ร {sobjectName}",
+ "schema.notifications.noRelationship.title": "Aucune relation trouvรฉe",
+ "schema.notifications.noRelationship.message": "Impossible d'ajouter \"{draggedSObject}\" au canevas. Aucune relation n'existe entre \"{mainSObject}\" et \"{draggedSObject}\". Seuls les SObjects liรฉs peuvent รชtre ajoutรฉs comme sous-requรชtes.",
+ "schema.notifications.childObjectAdded.title": "Objet enfant ajoutรฉ",
+ "schema.notifications.childObjectAdded.message": "{childSObjectName} ajoutรฉ avec les premiers {displayedFields} champs ({hiddenFields} autres champs disponibles mais non affichรฉs)",
+ "schema.notifications.reverseRelationshipAdded.title": "Relation ajoutรฉe",
+ "schema.notifications.reverseRelationshipAdded.message": "{sobjectName} ajoutรฉ comme sous-requรชte ร {parentSObject}. Utilisez le menu contextuel (clic droit) pour ajouter l'enfant correct si nรฉcessaire.",
+ "schema.tab.fieldName": "Nom du champ",
+ "schema.tab.fieldLabel": "รtiquette du champ",
+ "schema.tab.type": "Type",
+ "schema.tab.properties": "Propriรฉtรฉs et valeurs",
+ "schema.tab.objectField": "Objet / Champ",
+ "schema.tab.relationshipName": "Nom de la relation",
+ "schema.tab.cascadeDelete": "Suppression en cascade",
+ "schema.tab.noRelationshipsFound": "Aucune relation trouvรฉe",
+ "schema.tab.noFieldsFound": "Aucun champ trouvรฉ correspondant ร votre filtre",
+ "schema.tab.loadingFields": "Chargement des champs...",
+ "schema.tab.clearFilter": "Effacer le filtre",
+ "schema.tab.filterByName": "Filtrer par nom, รฉtiquette, type ou propriรฉtรฉs...",
+ "schema.tab.filterRelationships": "Filtrer les relations par objet, champ ou nom de relation...",
+ "schema.tab.exportPdf": "Exporter en PDF",
+ "schema.tab.collapseReport": "Rรฉduire le rapport",
+ "schema.tab.expandReport": "Dรฉvelopper le rapport",
+ "schema.tab.zoomIn": "Zoom avant",
+ "schema.tab.zoomOut": "Zoom arriรจre",
+ "schema.tab.resetView": "Rรฉinitialiser la vue",
+ "schema.tab.fitToCanvas": "Ajuster au canevas",
+ "schema.tab.clearGraph": "Effacer le graphique",
+ "schema.tab.showSchemaReport": "Afficher le rapport de schรฉma",
+ "schema.tab.showRelationshipsReport": "Afficher le rapport de relations",
+ "schema.tab.addingSObject": "Ajout de SObject",
+ "schema.tab.noSObjectsInGraph": "Aucun SObject dans le graphique de schรฉma",
+ "schema.tab.noSObjectsInGraphSubtitle": "Glissez les SObjects de l'arbre pour commencer ร construire votre graphique de relations",
+ "schema.tab.noSalesforceConnection": "Aucune connexion Salesforce disponible. Veuillez vous connecter ร Salesforce d'abord.",
+ "schema.tab.unknownError": "Erreur inconnue",
+ "schema.tab.totalFieldsTooltip": "Nombre total de champs dans cet objet",
+ "schema.tab.selectedFieldsTooltip": "Nombre de champs sรฉlectionnรฉs dans la requรชte",
+ "schema.tab.customObjectTooltip": "Ceci est un objet Salesforce personnalisรฉ",
+ "schema.tab.standardObjectTooltip": "Ceci est un objet Salesforce standard",
+ "schema.tab.noQueryToConvert": "Aucune requรชte ร convertir",
+ "schema.tab.noQueryToConvertMessage": "Veuillez d'abord ajouter des SObjects au canevas avant de convertir en requรชte SOQL.",
+ "schema.tab.referencesTooltip": "Rรฉfรฉrences : {referenceNames}",
+ "schema.tab.lengthTooltip": "Nombre maximum de caractรจres autorisรฉs : {length}",
+ "schema.tab.precisionTooltip": "Format numรฉrique : {precision} chiffres au total, {scale} dรฉcimales",
+ "schema.tab.standard": "Standard",
+ "schema.tab.custom": "Personnalisรฉ",
+ "schema.tab.generatedOn": "Gรฉnรฉrรฉ le : {date}",
+ "schema.tab.objectType": "Type d'objet : {type}",
+ "schema.tab.totalFields": "Champs totaux : {total} | Champs filtrรฉs : {filtered}",
+ "schema.tab.totalRelationships": "Relations totales : {count}",
+ "schema.tab.relationshipsFound": "{count} relations trouvรฉes",
+ "schema.tab.fieldsCount": "{filtered} sur {total} champs",
+ "schema.tab.fieldsCountAll": "{filtered} sur {total} champs (toutes les mรฉtadonnรฉes)",
+ "schema.tab.fieldsCountSelected": "{filtered} sur {total} champs (sรฉlectionnรฉs)",
+ "schema.tab.matchingFilter": "correspondant ร \"{filter}\"",
+ "schema.tab.picklistValues": "Valeurs de liste de sรฉlection",
+ "schema.fieldSelection.alreadySelected": "Sรฉlectionnรฉ",
+ "schema.fieldSelection.cancel": "Annuler",
+ "schema.fieldSelection.apply": "Appliquer",
+ "schema.fieldSelection.noNewFields.title": "Aucun nouveau champ",
+ "schema.fieldSelection.noNewFields.message": "Veuillez sรฉlectionner de nouveaux champs ร ajouter. Les champs dรฉjร dans le nลud ne sont pas comptรฉs.",
+ "schema.fieldSelection.noChanges.title": "Aucun changement",
+ "schema.fieldSelection.noChanges.message": "Veuillez sรฉlectionner ou dรฉsรฉlectionner des champs pour effectuer des changements.",
+ "schema.notifications.noQueryError.title": "Aucune requรชte disponible",
+ "schema.notifications.noQueryError.message": "Veuillez d'abord crรฉer une requรชte dans l'onglet Requรชte avant d'ajouter des champs.",
+ "schema.report.close": "Fermer le rapport",
+ "schema.modal.close": "Fermer la modal",
+ "schema.tab.actionsLabel": "Contrรดles du canevas",
+ "app.executing": "Exรฉcution en cours...",
+ "results.noMoreRecords": "Plus d'enregistrements",
+ "session.created.title": "Session crรฉรฉe",
+ "session.created.message": "Vous รชtes maintenant authentifiรฉ",
+ "session.ended.title": "Session terminรฉe",
+ "session.ended.message": "Vous avez รฉtรฉ dรฉconnectรฉ",
+ "settings.title": "Paramรจtres",
+ "settings.language": "Langue",
+ "settings.about": "ร propos",
+ "settings.info": "Informations",
+ "settings.savedConnections": "Connexions enregistrรฉes",
+ "settings.manage": "Gรฉrer",
+ "settings.logout": "Dรฉconnexion",
+ "settings.errors.retrieval_failed": "รchec de la rรฉcupรฉration des paramรจtres",
+ "settings.about.version": "Version",
+ "settings.about.buildDate": "Date de construction",
+ "settings.about.license": "Licence",
+ "settings.about.website": "Site web",
+ "settings.about.support": "Support",
+ "settings.about.feedback": "Commentaires",
+ "settings.about.author": "Auteur",
+ "settings.about.description": "Description",
+ "sobject_cache.messages.cache_created": "โ
Mรฉtadonnรฉes SObject mises en cache avec succรจs",
+ "sobject_cache.messages.cache_updated": "๐ Cache de mรฉtadonnรฉes SObject mis ร jour avec succรจs",
+ "sobject_cache.messages.cache_deleted": "โ
Entrรฉe de cache SObject supprimรฉe avec succรจs",
+ "sobject_cache.messages.cache_cleared": "โ
Toutes les entrรฉes de cache SObject effacรฉes avec succรจs",
+ "sobject_cache.messages.cache_retrieved": "โ
Entrรฉe de cache SObject rรฉcupรฉrรฉe avec succรจs",
+ "sobject_cache.errors.not_found": "โ Entrรฉe de cache SObject non trouvรฉe",
+ "sobject_cache.errors.invalid_cache_data": "โ Format de donnรฉes de cache invalide",
+ "sobject_cache.errors.sobject_not_cached": "โ SObject n'est pas mis en cache pour cette connexion",
+ "sobject_cache.errors.cache_creation_failed": "โ รchec de la crรฉation de l'entrรฉe de cache SObject",
+ "sobject_cache.errors.cache_update_failed": "โ รchec de la mise ร jour de l'entrรฉe de cache SObject",
+ "sobject_cache.errors.cache_deletion_failed": "โ รchec de la suppression de l'entrรฉe de cache SObject",
+ "sobject_cache.errors.cache_retrieval_failed": "โ รchec de la rรฉcupรฉration de l'entrรฉe de cache SObject",
+ "sobject_cache.errors.invalid_metadata": "โ Mรฉtadonnรฉes SObject invalides fournies",
+ "sobject_cache.errors.connection_not_found": "โ Connexion non trouvรฉe",
+ "sobject_cache.errors.database_error": "โ รchec de l'opรฉration de base de donnรฉes",
+ "sobject_cache.validation.connection_uuid_required": "L'UUID de connexion est requis",
+ "sobject_cache.validation.sobject_name_required": "Le nom SObject est requis",
+ "sobject_cache.validation.metadata_required": "Les mรฉtadonnรฉes SObject sont requises",
+ "sobject_cache.validation.invalid_sobject_name": "Format de nom SObject invalide",
+ "sobject_cache.validation.invalid_metadata_structure": "Structure de mรฉtadonnรฉes invalide",
+ "sobject_cache.operations.caching_metadata": "Mise en cache des mรฉtadonnรฉes SObject",
+ "sobject_cache.operations.retrieving_cache": "Rรฉcupรฉration de l'entrรฉe de cache SObject",
+ "sobject_cache.operations.updating_cache": "Mise ร jour de l'entrรฉe de cache SObject",
+ "sobject_cache.operations.deleting_cache": "Suppression de l'entrรฉe de cache SObject",
+ "sobject_cache.operations.listing_cache": "Listage des entrรฉes de cache SObject",
+ "sobject_cache.operations.clearing_cache": "Effacement de toutes les entrรฉes de cache SObject",
+ "sobject_cache.messages.retrieved_sobjects_successfully": "{count} SObjects rรฉcupรฉrรฉs avec succรจs",
+ "sobject_cache.messages.retrieved_metadata_successfully": "Mรฉtadonnรฉes rรฉcupรฉrรฉes pour {sobject_name} avec succรจs",
+ "sobject_cache.messages.statistics_retrieved_successfully": "Statistiques de cache rรฉcupรฉrรฉes avec succรจs",
+ "status.connected": "Connectรฉ",
+ "status.disconnected": "Dรฉconnectรฉ",
+ "status.connecting": "Connexion...",
+ "status.querying": "Requรชte en cours...",
+ "status.apiCalls": "Appels API : {used}/{limit} ({percentage}%)",
+ "status.describedSObjects": "{count} SObjects dรฉcrits",
+ "statusBar.sobjectsCount": "{count} SObjects dรฉcrits",
+ "statusBar.reload": "Recharger l'arbre SObjects",
+ "statusBar.reload.fallback": "Recharger",
+ "statusBar.connectionId": "ID de connexion ({id}...)",
+ "statusBar.apiUsage": "Utilisation API : {used}/{limit}",
+ "status.backend.ok": "Backend OK",
+ "status.backend.checking": "Vรฉrification...",
+ "status.backend.error": "Erreur du backend",
+ "toolbar.executing": "Exรฉcution...",
+ "toolbar.executing.fallback": "Exรฉcution...",
+ "toolbar.executeQuery": "Exรฉcuter la requรชte",
+ "toolbar.executeQuery.fallback": "Exรฉcuter la requรชte",
+ "toolbar.executeQueryAll": "Exรฉcuter tout",
+ "toolbar.executeQueryAll.fallback": "Exรฉcuter tout",
+ "toolbar.queryMore": "Requรชte supplรฉmentaire",
+ "toolbar.queryMore.fallback": "Requรชte supplรฉmentaire",
+ "toolbar.querying": "Requรชte en cours...",
+ "toolbar.querying.fallback": "Requรชte en cours...",
+ "toolbar.saveQuery": "Enregistrer la requรชte",
+ "toolbar.loadQuery": "Charger la requรชte",
+ "toolbar.clearQuery": "Effacer la requรชte",
+ "toolbar.exportResults": "Exporter les rรฉsultats",
+ "toolbar.importQuery": "Importer la requรชte"
+}
\ No newline at end of file
diff --git a/dashboard/src/services/ApiService.ts b/dashboard/src/services/ApiService.ts
index 6de2e06..76fe15a 100644
--- a/dashboard/src/services/ApiService.ts
+++ b/dashboard/src/services/ApiService.ts
@@ -114,7 +114,6 @@ export class ApiService {
private onConnectionError?: () => void;
private lastConnectionErrorTime: number = 0;
private connectionErrorDebounceMs: number = 2000; // 2 seconds // Callback for connection errors
- private translationsCache: Map = new Map(); // Cache for translations
private constructor() {
// Get configuration from app config
this.baseUrl = this.getBaseUrlFromConfig();
@@ -343,13 +342,6 @@ export class ApiService {
return this.client.delete(url, config);
}
- /**
- * Clear translations cache
- */
- clearTranslationsCache(): void {
- this.translationsCache.clear();
- logger.debug(' Translations cache cleared', 'ApiService');
- }
// ========================================
// AVAILABILITY CHECK
@@ -618,6 +610,48 @@ export class ApiService {
/**
* Get all connections (REST compliant)
*/
+ /**
+ * Get lightweight connections list with pagination (PREFERRED)
+ * Optimized for performance - no credentials included
+ */
+ async getAllConnectionsLightweight(page: number = 1, pageSize: number = 25): Promise<{
+ connections: any[];
+ total_count: number;
+ page: number;
+ page_size: number;
+ total_pages: number;
+ }> {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ logger.debug('Fetching lightweight connections', 'ApiService', { page, pageSize });
+ const masterKey = getMasterKeyFromSession();
+
+ const response = await this.client.get(
+ this.addLangToUrl(`${this.getEndpointUrl('connections')}/lightweight`),
+ {
+ params: {
+ page,
+ page_size: pageSize
+ },
+ headers: {
+ 'X-Master-Key': masterKey
+ }
+ }
+ );
+ return response.data;
+ } catch (error: any) {
+ const errorDetail = error.response?.data?.detail || 'Failed to get lightweight connections';
+ throw new Error(errorDetail);
+ }
+ }
+
+ /**
+ * Get all connections (DEPRECATED - use getAllConnectionsLightweight instead)
+ * Kept for backward compatibility but has poor performance with many connections
+ */
async getAllConnections(): Promise {
if (!this.isAvailable) {
throw new Error('Python backend not available');
@@ -644,6 +678,7 @@ export class ApiService {
/**
* Get connection with decrypted credentials (REST compliant)
+ * Optimized endpoint for fetching full connection details including credentials
*/
async getConnectionWithCredentials(connectionUuid: string): Promise {
if (!this.isAvailable) {
@@ -670,6 +705,36 @@ export class ApiService {
}
}
+ /**
+ * Get only the credentials for a specific connection (lazy-load optimization)
+ * Use this with getAllConnectionsLightweight for two-stage loading
+ */
+ async getConnectionCredentials(connectionUuid: string): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const masterKey = getMasterKeyFromSession();
+
+ const response = await this.client.get(
+ this.addLangToUrl(`${this.getEndpointUrl('connections')}/${connectionUuid}/credentials`),
+ {
+ headers: {
+ 'X-Master-Key': masterKey
+ }
+ }
+ );
+ return response.data;
+ } catch (error: any) {
+ if (error.response?.status === 404) {
+ return null;
+ }
+ const errorDetail = error.response?.data?.detail || 'Failed to get connection credentials';
+ throw new Error(errorDetail);
+ }
+ }
+
/**
* Delete connection (REST compliant)
*/
@@ -728,234 +793,130 @@ export class ApiService {
}
/**
- * Update connection display name (REST compliant)
+ * Test connection credentials without saving (REST compliant)
*/
- async updateConnection(connectionUuid: string, displayName: string): Promise {
+ async testConnectionCredentials(connectionData: {
+ username: string;
+ password: string;
+ environment: string;
+ consumerKey?: string;
+ consumerSecret?: string;
+ securityToken?: string;
+ clientId?: string;
+ clientSecret?: string;
+ }): Promise<{ success: boolean; message: string; user_info?: any }> {
if (!this.isAvailable) {
throw new Error('Python backend not available');
}
try {
- // Get master key for authentication
const masterKey = getMasterKeyFromSession();
- const response = await this.client.put(this.addLangToUrl(`${this.getEndpointUrl('connections')}/${connectionUuid}`), {
- display_name: displayName,
- master_key: masterKey
- });
- return response.data;
- } catch (error: any) {
- // Return the i18n key from the API response
- const errorDetail = error.response?.data?.detail || 'Failed to update connection';
- throw new Error(errorDetail);
- }
- }
-
- // ========================================
- // I18N ENDPOINTS
- // ========================================
-
- /**
- * Get all languages (REST compliant)
- */
- async getAllLanguages(): Promise {
- if (!this.isAvailable) {
- throw new Error('Python backend not available');
- }
-
- try {
- const response = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/languages`));
- return response.data;
- } catch (error: any) {
- throw new Error(error.response?.data?.detail || 'Failed to get languages');
- }
- }
-
- /**
- * Get all languages with optional filtering (REST compliant)
- */
- async getAllLanguagesWithFilter(isActive?: boolean): Promise {
- if (!this.isAvailable) {
- throw new Error('Python backend not available');
- }
-
- try {
- let url = `${this.getEndpointUrl('i18n')}/languages`;
- if (isActive !== undefined) {
- url += `?is_active=${isActive}`;
- }
-
- const response = await this.client.get(this.addLangToUrl(url));
- return response.data;
- } catch (error: any) {
- throw new Error(error.response?.data?.detail || 'Failed to get languages');
- }
- }
-
- /**
- * Get available locales (REST compliant) - only active languages
- */
- async getAvailableLocales(): Promise> {
- if (!this.isAvailable) {
- throw new Error('Python backend not available');
- }
-
- try {
- logger.debug('Loading available languages', 'ApiService');
- const response = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/languages?is_active=true&fields=code,name`));
- logger.debug('Languages response received', 'ApiService', { count: response.data?.length });
+ const response = await this.client.post(
+ this.addLangToUrl(`${this.getEndpointUrl('connections')}/test`),
+ {
+ connection_data: {
+ username: connectionData.username,
+ password: connectionData.password,
+ environment: connectionData.environment,
+ consumer_key: connectionData.consumerKey || null,
+ consumer_secret: connectionData.consumerSecret || null,
+ security_token: connectionData.securityToken || null,
+ client_id: connectionData.clientId || null,
+ client_secret: connectionData.clientSecret || null,
+ }
+ },
+ { headers: { 'X-Master-Key': masterKey } }
+ );
return response.data;
} catch (error: any) {
- logger.error('Error getting available locales', 'ApiService', error);
- throw new Error(error.response?.data?.detail || 'Failed to get available locales');
- }
- }
-
- /**
- * Get translations by locale (REST compliant)
- * This method gets all available pages first, then fetches translations for each page
- * Uses caching to prevent repeated API calls
- */
- async getTranslationsByLocale(locale: string): Promise {
- // Force a fresh availability check instead of relying on cached flag
- await this.checkAvailability();
- if (!this.isAvailable) {
- throw new Error('Python backend not available');
- }
-
- // Check cache first (5 minute TTL)
- const cacheKey = locale;
- const cached = this.translationsCache.get(cacheKey);
- const now = Date.now();
- const cacheTTL = 5 * 60 * 1000; // 5 minutes
-
- if (cached && (now - cached.timestamp) < cacheTTL) {
- logger.debug(' Using cached translations', 'ApiService', { locale, cacheAge: now - cached.timestamp });
- return cached.data;
- }
-
- try {
- logger.debug(' Loading translations from API', 'ApiService', { locale });
-
- // First, get all available pages
- const pagesResponse = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/translations/pages`));
- const availablePages = pagesResponse.data;
-
- // Then, get translations for each page
- const allTranslations = [];
- for (const pageName of availablePages) {
- try {
- const pageResponse = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/translations/${locale}/${pageName}`));
- if (pageResponse.data) {
- allTranslations.push(pageResponse.data);
+ let errorMessage = 'Connection test failed';
+ if (error.response?.data?.detail) {
+ const detail = error.response.data.detail;
+ if (typeof detail === 'object' && detail.message) {
+ errorMessage = detail.message;
+ if (detail.field_errors) {
+ const vals = Object.values(detail.field_errors);
+ if (vals.length > 0) errorMessage = String(vals[0]);
}
- } catch (pageError: any) {
- // Log warning but continue with other pages
- logger.warn(`Failed to load translations for page ${pageName}`, 'ApiService', pageError);
+ } else if (typeof detail === 'string') {
+ errorMessage = detail;
}
+ } else if (error.message) {
+ errorMessage = error.message;
}
-
- // Cache the result
- this.translationsCache.set(cacheKey, { data: allTranslations, timestamp: now });
- logger.debug(' Cached translations', 'ApiService', { locale, count: allTranslations.length });
-
- return allTranslations;
- } catch (error: any) {
- throw new Error(error.response?.data?.detail || 'Failed to get translations by locale');
+ throw new Error(errorMessage);
}
}
/**
- * Get translation by page (REST compliant)
+ * Update connection display name and/or credentials (REST compliant)
*/
- async getTranslationByPage(locale: string, pageName: string): Promise {
- if (!this.isAvailable) {
- throw new Error('Python backend not available');
- }
-
- try {
- const response = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/translations/${locale}/${pageName}`));
- return response.data;
- } catch (error: any) {
- if (error.response?.status === 404) {
- return null;
- }
- throw new Error(error.response?.data?.detail || 'Failed to get translation by page');
+ async updateConnection(
+ connectionUuid: string,
+ displayName: string,
+ connectionData?: {
+ username: string;
+ password: string;
+ environment: string;
+ consumerKey?: string;
+ consumerSecret?: string;
+ securityToken?: string;
+ clientId?: string;
+ clientSecret?: string;
}
- }
-
- /**
- * Get translation key (REST compliant)
- */
- async getTranslationKey(locale: string, key: string): Promise {
+ ): Promise {
if (!this.isAvailable) {
throw new Error('Python backend not available');
}
try {
- const response = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/translations/${locale}/key/${encodeURIComponent(key)}`));
- return response.data.value;
- } catch (error: any) {
- if (error.response?.status === 404) {
- return null;
- }
- throw new Error(error.response?.data?.detail || 'Failed to get translation key');
- }
- }
-
- /**
- * Get available locale objects (REST compliant) - only active languages
- */
- async getAvailableLocaleObjects(): Promise {
- if (!this.isAvailable) {
- throw new Error('Python backend not available');
- }
+ const masterKey = getMasterKeyFromSession();
- try {
- logger.debug('Loading locale objects', 'ApiService');
- const response = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/locales/objects?is_active=true`));
- logger.debug('Locale objects response received', 'ApiService', { count: response.data?.length });
- return response.data;
- } catch (error: any) {
- logger.error('Error getting available locale objects', 'ApiService', error);
- throw new Error(error.response?.data?.detail || 'Failed to get available locale objects');
- }
- }
+ const body: any = {
+ display_name: displayName,
+ master_key: masterKey
+ };
- /**
- * Set default language (REST compliant)
- */
- async setDefaultLanguage(languageUuid: string): Promise {
- if (!this.isAvailable) {
- throw new Error('Python backend not available');
- }
+ if (connectionData) {
+ body.connection_data = {
+ username: connectionData.username,
+ password: connectionData.password,
+ environment: connectionData.environment,
+ consumer_key: connectionData.consumerKey || null,
+ consumer_secret: connectionData.consumerSecret || null,
+ security_token: connectionData.securityToken || null,
+ client_id: connectionData.clientId || null,
+ client_secret: connectionData.clientSecret || null,
+ };
+ }
- try {
- logger.debug(`Calling PUT /i18n/languages/${languageUuid}/default`, 'ApiService');
- const response = await this.client.put(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/languages/${languageUuid}/default`));
- logger.debug('Response received', 'ApiService', response.data);
+ const response = await this.client.put(
+ this.addLangToUrl(`${this.getEndpointUrl('connections')}/${connectionUuid}`),
+ body
+ );
return response.data;
} catch (error: any) {
- logger.error('Error setting default language', 'ApiService', error);
- throw new Error(error.response?.data?.detail || 'Failed to set default language');
+ let errorMessage = 'Failed to update connection';
+ if (error.response?.data?.detail) {
+ const detail = error.response.data.detail;
+ if (typeof detail === 'object' && detail.message) {
+ errorMessage = detail.message;
+ if (detail.field_errors) {
+ const fieldErrors = Object.values(detail.field_errors);
+ if (fieldErrors.length > 0) errorMessage = String(fieldErrors[0]);
+ }
+ } else if (typeof detail === 'string') {
+ errorMessage = detail;
+ }
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+ throw new Error(errorMessage);
}
}
- /**
- * Get translation stats (REST compliant)
- */
- async getTranslationStats(): Promise {
- if (!this.isAvailable) {
- throw new Error('Python backend not available');
- }
-
- try {
- const response = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('i18n')}/translations/stats`));
- return response.data;
- } catch (error: any) {
- throw new Error(error.response?.data?.detail || 'Failed to get translation stats');
- }
- }
+ // I18N ENDPOINTS REMOVED โ translations now served from local JSON files in src/i18n/
// ========================================
// AUTH PROVIDERS ENDPOINTS
@@ -1248,12 +1209,13 @@ export class ApiService {
return response.data;
} catch (error: any) {
- logger.error('/connections/{uuid}/connect endpoint failed', 'ApiService', {
+ logger.error('/connections/{uuid}/connect endpoint failed', 'ApiService', {
error: error.response?.data?.detail || error.message,
status: error.response?.status,
data: error.response?.data
});
- throw new Error(error.response?.data?.detail || 'Failed to connect to Salesforce');
+ // The axios response interceptor will handle the error notification
+ throw error;
}
}
@@ -1769,11 +1731,350 @@ export class ApiService {
}
// ========================================
- // QUERY HISTORY ENDPOINTS
+ // SAVED APEX ENDPOINTS
// ========================================
+ /**
+ * Get all saved Apex code for a connection
+ */
+ async getSavedApexList(connectionUuid: string, params?: { limit?: number; offset?: number; search?: string }): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const queryParams = new URLSearchParams();
+ queryParams.append('connection_uuid', connectionUuid);
+ if (params?.limit) queryParams.append('limit', String(params.limit));
+ if (params?.offset) queryParams.append('offset', String(params.offset));
+ if (params?.search) queryParams.append('search', params.search);
+
+ const response = await this.client.get(this.addLangToUrl(`${this.getEndpointUrl('savedApex')}/?${queryParams.toString()}`));
+ return response.data.saved_apex_list || [];
+ } catch (error: any) {
+ throw new Error(error.response?.data?.detail || 'Failed to get saved Apex code');
+ }
+ }
+
+ /**
+ * Create a new saved Apex code
+ */
+ async createSavedApex(params: {
+ connection_uuid: string;
+ name: string;
+ apex_code: string;
+ code_type: string;
+ description?: string;
+ tags?: string;
+ is_favorite?: boolean;
+ debug_levels?: {
+ DB: string;
+ Workflow: string;
+ Validation: string;
+ Callouts: string;
+ Apex_Code: string;
+ Apex_Profiling: string;
+ };
+ }): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const response = await this.client.post(this.addLangToUrl(`${this.getEndpointUrl('savedApex')}/`), params);
+ return response.data.saved_apex_uuid;
+ } catch (error: any) {
+ let errorMessage = 'Failed to create saved Apex code';
+
+ if (error.response?.data?.detail) {
+ const detail = error.response.data.detail;
+
+ if (typeof detail === 'object') {
+ if (detail.message && typeof detail.message === 'string') {
+ errorMessage = detail.message;
+ } else if (detail.error_code && typeof detail.error_code === 'string') {
+ errorMessage = detail.error_code;
+ } else {
+ errorMessage = JSON.stringify(detail);
+ }
+ } else if (typeof detail === 'string') {
+ errorMessage = detail;
+ }
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
+ throw new Error(errorMessage);
+ }
+ }
+
+ /**
+ * Update a saved Apex code
+ */
+ async updateSavedApex(savedApexUuid: string, params: {
+ name?: string;
+ apex_code?: string;
+ code_type?: string;
+ description?: string;
+ tags?: string;
+ is_favorite?: boolean;
+ debug_levels?: {
+ DB: string;
+ Workflow: string;
+ Validation: string;
+ Callouts: string;
+ Apex_Code: string;
+ Apex_Profiling: string;
+ };
+ }): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const response = await this.client.put(this.addLangToUrl(`${this.getEndpointUrl('savedApex')}/${savedApexUuid}`), params);
+ return response.data;
+ } catch (error: any) {
+ throw new Error(error.response?.data?.detail || 'Failed to update saved Apex code');
+ }
+ }
+
+ /**
+ * Delete a saved Apex code
+ */
+ async deleteSavedApex(savedApexUuid: string): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ await this.client.delete(this.addLangToUrl(`${this.getEndpointUrl('savedApex')}/${savedApexUuid}`));
+ } catch (error: any) {
+ throw new Error(error.response?.data?.detail || 'Failed to delete saved Apex code');
+ }
+ }
+
+ /**
+ * Toggle favorite status for a saved Apex code
+ */
+ async toggleApexFavorite(savedApexUuid: string): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const response = await this.client.post(this.addLangToUrl(`${this.getEndpointUrl('savedApex')}/${savedApexUuid}/toggle-favorite`), {});
+ return response.data;
+ } catch (error: any) {
+ throw new Error(error.response?.data?.detail || 'Failed to toggle favorite status');
+ }
+ }
+
+ /**
+ * Execute anonymous Apex code
+ */
+ async executeAnonymousApex(connectionUuid: string, params: {
+ apex_code: string;
+ debug_levels?: {
+ DB: string;
+ Workflow: string;
+ Validation: string;
+ Callouts: string;
+ Apex_Code: string;
+ Apex_Profiling: string;
+ };
+ }): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const response = await this.client.post(
+ this.addLangToUrl(`${this.getEndpointUrl('salesforce')}/apex/execute-anonymous?connection_uuid=${encodeURIComponent(connectionUuid)}`),
+ {
+ apex_code: params.apex_code,
+ debug_levels: params.debug_levels
+ }
+ );
+ return response.data;
+ } catch (error: any) {
+ let errorMessage = 'Failed to execute Apex code';
+
+ if (error.response?.data?.detail) {
+ const detail = error.response.data.detail;
+
+ if (typeof detail === 'object') {
+ if (detail.message && typeof detail.message === 'string') {
+ errorMessage = detail.message;
+ } else if (detail.error_code && typeof detail.error_code === 'string') {
+ errorMessage = detail.error_code;
+ } else {
+ errorMessage = JSON.stringify(detail);
+ }
+ } else if (typeof detail === 'string') {
+ errorMessage = detail;
+ }
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
+ throw new Error(errorMessage);
+ }
+ }
+ async executeSavedApex(apex_uuid: string, connection_uuid: string): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+ try {
+ const response = await this.client.post(
+ this.addLangToUrl(`${this.getEndpointUrl('savedApex')}/${encodeURIComponent(apex_uuid)}/execute?connection_uuid=${encodeURIComponent(connection_uuid)}`)
+ );
+ return response.data;
+ } catch (error: any) {
+ let errorMessage = 'Failed to execute saved Apex code';
+
+ if (error.response?.data?.detail) {
+ const detail = error.response.data.detail;
+
+ if (typeof detail === 'object') {
+ if (detail.message && typeof detail.message === 'string') {
+ errorMessage = detail.message;
+ } else if (detail.error_code && typeof detail.error_code === 'string') {
+ errorMessage = detail.error_code;
+ } else {
+ errorMessage = JSON.stringify(detail);
+ }
+ } else if (typeof detail === 'string') {
+ errorMessage = detail;
+ }
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
+ throw new Error(errorMessage);
+ }
+ }
+
+ /**
+ * Run Apex tests
+ */
+ async runApexTests(connectionUuid: string, params: {
+ test_classes?: string[];
+ test_methods?: string[];
+ }): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const response = await this.client.post(
+ this.addLangToUrl(`${this.getEndpointUrl('salesforce')}/apex/run-tests?connection_uuid=${encodeURIComponent(connectionUuid)}`),
+ {
+ test_classes: params.test_classes,
+ test_methods: params.test_methods
+ }
+ );
+ return response.data;
+ } catch (error: any) {
+ let errorMessage = 'Failed to run Apex tests';
+
+ if (error.response?.data?.detail) {
+ const detail = error.response.data.detail;
+
+ if (typeof detail === 'object') {
+ if (detail.message && typeof detail.message === 'string') {
+ errorMessage = detail.message;
+ } else if (detail.error_code && typeof detail.error_code === 'string') {
+ errorMessage = detail.error_code;
+ } else {
+ errorMessage = JSON.stringify(detail);
+ }
+ } else if (typeof detail === 'string') {
+ errorMessage = detail;
+ }
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
+ throw new Error(errorMessage);
+ }
+ }
+
+ async getApexClasses(connectionUuid: string): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const response = await this.client.get(
+ this.addLangToUrl(`${this.getEndpointUrl('salesforce')}/apex/classes?connection_uuid=${connectionUuid}`)
+ );
+ return response.data;
+ } catch (error: any) {
+ let errorMessage = 'Failed to retrieve Apex classes';
+
+ if (error.response?.data?.detail) {
+ const detail = error.response.data.detail;
+
+ if (typeof detail === 'object') {
+ if (detail.message && typeof detail.message === 'string') {
+ errorMessage = detail.message;
+ } else if (detail.error_code && typeof detail.error_code === 'string') {
+ errorMessage = detail.error_code;
+ } else {
+ errorMessage = JSON.stringify(detail);
+ }
+ } else if (typeof detail === 'string') {
+ errorMessage = detail;
+ }
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
+ throw new Error(errorMessage);
+ }
+ }
+
+ async getApexTriggers(connectionUuid: string): Promise {
+ if (!this.isAvailable) {
+ throw new Error('Python backend not available');
+ }
+
+ try {
+ const response = await this.client.get(
+ this.addLangToUrl(`${this.getEndpointUrl('salesforce')}/apex/triggers?connection_uuid=${connectionUuid}`)
+ );
+ return response.data;
+ } catch (error: any) {
+ let errorMessage = 'Failed to retrieve Apex triggers';
+
+ if (error.response?.data?.detail) {
+ const detail = error.response.data.detail;
+
+ if (typeof detail === 'object') {
+ if (detail.message && typeof detail.message === 'string') {
+ errorMessage = detail.message;
+ } else if (detail.error_code && typeof detail.error_code === 'string') {
+ errorMessage = detail.error_code;
+ } else {
+ errorMessage = JSON.stringify(detail);
+ }
+ } else if (typeof detail === 'string') {
+ errorMessage = detail;
+ }
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
+ throw new Error(errorMessage);
+ }
+ }
+
+ // ========================================
+ // QUERY HISTORY ENDPOINTS
+ // ========================================
diff --git a/dashboard/src/services/ConnectionManager.ts b/dashboard/src/services/ConnectionManager.ts
index 2e39ef2..219c8e0 100644
--- a/dashboard/src/services/ConnectionManager.ts
+++ b/dashboard/src/services/ConnectionManager.ts
@@ -124,43 +124,55 @@ export class ConnectionManager {
}
/**
- * Get all saved connections from Python backend
+ * Get all saved connections from Python backend (lightweight version)
+ * Uses the new optimized /connections/lightweight endpoint for better performance
*/
async getAllConnections(): Promise {
try {
- logger.debug(' Loading all connections from Python backend', 'ConnectionManager');
-
- const apiConnections = await apiService.getAllConnections();
+ logger.debug('Loading lightweight connections from Python backend', 'ConnectionManager');
+
+ // Use lightweight endpoint for first page with default size
+ // This is all we need for the UI without credentials
+ const response = await apiService.getAllConnectionsLightweight(1, 50);
+ const apiConnections = response.connections;
const savedConnections: SavedConnection[] = [];
-
+
for (const apiConnection of apiConnections) {
try {
- // Transform API response to SavedConnection format
+ // Parse lastUsed date from backend-formatted string (ISO date format)
+ let lastUsedTime = Date.now();
+ if (apiConnection.last_used) {
+ lastUsedTime = new Date(apiConnection.last_used).getTime();
+ }
+
+ // Transform lightweight API response to SavedConnection format
+ // Note: credentials are NOT included - they'll be loaded on-demand
const savedConnection: SavedConnection = {
id: apiConnection.connection_uuid?.replace(/^"(.*)"$/, '$1'), // Strip quotes if present
- oauthType: apiConnection.auth_provider_id === 'auth-provider-sf-classic-001' ? OAuthType.SALESFORCE_CLASSIC : OAuthType.OAUTH_STANDARD,
- username: apiConnection.connection_data.username,
- environment: apiConnection.connection_data.environment as 'production' | 'sandbox',
+ oauthType: OAuthType.OAUTH_STANDARD, // Will be determined from auth_provider_uuid if needed
+ username: apiConnection.username || 'Unknown', // Username from lightweight response
+ environment: (apiConnection.environment as 'production' | 'sandbox') || 'production',
displayName: apiConnection.display_name,
- lastUsed: new Date(apiConnection.updated_at).getTime(),
- isActive: true, // All connections from API are active
- consumerKey: apiConnection.connection_data.consumer_key,
- consumerSecret: apiConnection.connection_data.consumer_secret,
- securityToken: apiConnection.connection_data.security_token,
- clientId: apiConnection.connection_data.client_id,
- clientSecret: apiConnection.connection_data.client_secret
+ lastUsed: lastUsedTime,
+ isActive: apiConnection.is_connection_active,
+ // Credentials not included in lightweight response
+ consumerKey: undefined,
+ consumerSecret: undefined,
+ securityToken: undefined,
+ clientId: undefined,
+ clientSecret: undefined
};
-
+
savedConnections.push(savedConnection);
} catch (error) {
- logger.error('Failed to transform connection data', 'ConnectionManager', { connectionUuid: apiConnection.connection_uuid }, error as Error);
+ logger.error('Failed to transform lightweight connection data', 'ConnectionManager', { connectionUuid: apiConnection.connection_uuid }, error as Error);
}
}
-
+
return savedConnections;
-
+
} catch (error) {
- logger.error('Failed to load connections from API', 'ConnectionManager', null, error as Error);
+ logger.error('Failed to load lightweight connections from API', 'ConnectionManager', null, error as Error);
throw error;
}
}
@@ -202,6 +214,27 @@ export class ConnectionManager {
+ /**
+ * Clear sensitive credentials from a connection object
+ * Call this after using credentials to free up memory
+ */
+ clearCredentials(connection: SavedConnectionWithCredentials): SavedConnection {
+ return {
+ id: connection.id,
+ oauthType: connection.oauthType,
+ username: connection.username,
+ environment: connection.environment,
+ displayName: connection.displayName,
+ lastUsed: connection.lastUsed,
+ isActive: connection.isActive,
+ consumerKey: undefined,
+ consumerSecret: undefined,
+ securityToken: undefined,
+ clientId: undefined,
+ clientSecret: undefined
+ };
+ }
+
/**
* Delete a connection via Python backend
*/
diff --git a/dashboard/src/services/I18nService.ts b/dashboard/src/services/I18nService.ts
index 2703320..f18b091 100644
--- a/dashboard/src/services/I18nService.ts
+++ b/dashboard/src/services/I18nService.ts
@@ -1,31 +1,26 @@
+// Local-file-driven translations โ no backend calls needed
-// Database-driven translations only
+import en from '../i18n/en.json';
+import fr from '../i18n/fr.json';
-import { apiService } from './ApiService';
-import { appSettingsService } from './AppSettingsService';
-import { logger } from './Logger';
+type Translations = Record;
-// ========================================
-// I18N SERVICE
-// ========================================
+const LOCALES: Record = { en, fr };
+
+// Metadata for the locale switcher UI
+const LOCALE_META: Array<{ code: string; name: string }> = [
+ { code: 'en', name: 'English' },
+ { code: 'fr', name: 'French' },
+];
class I18nService {
private static instance: I18nService;
private currentLocale: string;
- private fallbackLocale: string = 'en';
- private databaseInitialized: boolean = false;
- private translationCache: Map = new Map(); // Cache for sync translations
- private reloadAttempts?: Set; // Track reload attempts to prevent infinite loops
- private reloadTimeout?: NodeJS.Timeout; // Debounce reload attempts
- private lastReloadTime?: number; // Track when we last reloaded to prevent rapid reloads
- private initializationPromise?: Promise; // Track ongoing initialization to prevent multiple concurrent calls
- private localeChangeTimeout?: NodeJS.Timeout; // Debounce locale changes to prevent infinite loops
+ private readonly fallbackLocale: string = 'en';
private constructor() {
- this.currentLocale = localStorage.getItem('datapilot_locale') || 'en';
-
- // Don't initialize immediately - wait for first use or explicit initialization
- logger.debug('I18nService created, will initialize on first use', 'I18nService');
+ const saved = localStorage.getItem('datapilot_locale') || 'en';
+ this.currentLocale = LOCALES[saved] ? saved : 'en';
}
static getInstance(): I18nService {
@@ -35,261 +30,48 @@ class I18nService {
return I18nService.instance;
}
- // ========================================
- // DATABASE INITIALIZATION
- // ========================================
+ // โโ Core lookup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- private async initializeDatabaseTranslations(): Promise {
- // Prevent multiple concurrent initializations
- if (this.initializationPromise) {
- logger.debug('Initialization already in progress, waiting for completion', 'I18nService');
- return this.initializationPromise;
- }
-
- this.initializationPromise = this._doInitializeDatabaseTranslations();
-
- try {
- await this.initializationPromise;
- } finally {
- this.initializationPromise = undefined;
- }
+ private lookup(key: string): string | undefined {
+ const dict = LOCALES[this.currentLocale];
+ if (dict?.[key]) return dict[key];
+ // Fallback to English
+ return LOCALES[this.fallbackLocale]?.[key];
}
-
- private async _doInitializeDatabaseTranslations(): Promise {
- const maxRetries = 3;
- const retryDelay = 1000; // 1 second
-
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
- try {
- logger.debug('Attempting to initialize translations', 'I18nService', { attempt, maxRetries });
-
- // Check if backend is available before making the call
- if (!apiService.isAvailable) {
- logger.debug('Backend not available, skipping translations load', 'I18nService');
- throw new Error('Backend not available');
- }
-
- // Use Python backend API to get translations
- const translations = await apiService.getTranslationsByLocale(this.currentLocale);
- logger.debug(' Received translations from API', 'I18nService', {
- count: translations.length,
- locale: this.currentLocale
- });
-
- // Populate the translation cache
- for (const translation of translations) {
- try {
- // translations_data is already a parsed object from the Python backend
- const translationsData = translation.translations_data || {};
- logger.debug(' Processing translation page', 'I18nService', {
- pageName: translation.page_name,
- keysCount: Object.keys(translationsData).length
- });
-
- for (const [key, value] of Object.entries(translationsData)) {
- const cacheKey = `${this.currentLocale}:${key}`;
- this.translationCache.set(cacheKey, value as string);
- }
- } catch (error) {
- logger.warn(' Failed to process translation data', 'I18nService', {
- pageName: translation.page_name,
- error: error instanceof Error ? error.message : String(error)
- });
- }
- }
-
- this.databaseInitialized = true;
- return; // Success, exit the retry loop
-
- } catch (error) {
- logger.error(' Failed to initialize Python backend translations', 'I18nService', {
- attempt,
- maxRetries,
- error: error instanceof Error ? error.message : String(error)
- });
-
- if (attempt === maxRetries) {
- // Last attempt failed, throw the error
- throw error;
- }
-
- // Wait before retrying
- await new Promise(resolve => setTimeout(resolve, retryDelay));
- }
- }
- }
-
-
- // ========================================
- // TRANSLATION METHODS
- // ========================================
-
- // Get translation for a key (async version)
- async t(key: string, params?: { [key: string]: string | number }): Promise {
- // Initialize on first use if not already initialized
- if (!this.databaseInitialized) {
- try {
- await this.initializeDatabaseTranslations();
- } catch (error) {
- logger.error('Failed to initialize translations on first use', 'I18nService', { error: error instanceof Error ? error.message : String(error) });
- return key; // Return key as fallback
- }
- }
-
- try {
- // Try current locale first
- let value = this.translationCache.get(key);
-
- // Fallback to default locale if not found
- if (!value && this.currentLocale !== this.fallbackLocale) {
- // Try to get fallback translations if not already loaded
- if (this.translationCache.size === 0) {
- await this.loadFallbackTranslations();
- }
- value = this.translationCache.get(key);
- }
-
- // Return key if no translation found
- if (!value) {
- return key;
- }
-
- return this.replaceParams(value, params);
- } catch (error) {
- logger.warn('Translation lookup failed', 'I18nService', { key, error: error instanceof Error ? error.message : String(error) });
- return key;
- }
+ private replaceParams(text: string, params?: Record): string {
+ if (!params) return text;
+ return text.replace(/\{(\w+)\}/g, (match, k) => params[k]?.toString() ?? match);
}
- private async loadFallbackTranslations(): Promise {
- try {
- // Check if backend is available before making the call
- if (!apiService.isAvailable) {
- logger.debug('Backend not available, skipping fallback translations load', 'I18nService');
- return;
- }
-
- const translations = await apiService.getTranslationsByLocale(this.fallbackLocale);
- for (const translation of translations) {
- try {
- // translations_data is already a parsed object from the Python backend
- const translationsData = translation.translations_data || {};
- for (const [key, value] of Object.entries(translationsData)) {
- const cacheKey = `${this.fallbackLocale}:${key}`;
- this.translationCache.set(cacheKey, value as string);
- }
- } catch (error) {
- logger.warn(' Failed to process fallback translation data', 'I18nService', {
- pageName: translation.page_name,
- error: error instanceof Error ? error.message : String(error)
- });
- }
- }
- } catch (error) {
- logger.warn(' Failed to load fallback translations', 'I18nService', { error: error instanceof Error ? error.message : String(error) });
- }
- }
+ // โโ Public translation API (same signatures as before) โโโโโโโโโโโโโโโโโโโ
- // Get translation for a key (sync version for React components)
- tSync(key: string, fallbackOrParams?: string | { [key: string]: string | number }, params?: { [key: string]: string | number }): string {
- // Intelligently detect if second parameter is fallback string or params object
+ tSync(key: string, fallbackOrParams?: string | Record, params?: Record): string {
let fallback: string | undefined;
- let actualParams: { [key: string]: string | number } | undefined;
-
+ let actualParams: Record | undefined;
+
if (typeof fallbackOrParams === 'string') {
- // Second parameter is a fallback string
fallback = fallbackOrParams;
actualParams = params;
- } else if (fallbackOrParams && typeof fallbackOrParams === 'object') {
- // Second parameter is params object (old signature)
+ } else {
actualParams = fallbackOrParams;
}
-
- // If database is not initialized yet, return fallback or key and trigger async initialization
- if (!this.databaseInitialized) {
- // Only trigger initialization if not already in progress
- if (!this.initializationPromise) {
- this.initializationPromise = this.initializeDatabaseTranslations().catch(error => {
- logger.warn(' Failed to initialize translations', 'I18nService', { key, error: error instanceof Error ? error.message : String(error) });
- // Clear the promise so it can be retried later
- this.initializationPromise = undefined;
- });
- }
- return fallback || key;
- }
-
- // Try current locale first using local cache
- let value = this.translationCache.get(`${this.currentLocale}:${key}`);
-
- // Try fallback locale if not found
- if (!value && this.currentLocale !== this.fallbackLocale) {
- // Try to get fallback translations if not already loaded
- if (this.translationCache.size === 0) {
- // Trigger async fallback loading in background
- this.loadFallbackTranslations().catch(error => {
- logger.warn(' Failed to load fallback translations', 'I18nService', { error: error instanceof Error ? error.message : String(error) });
- });
- }
- value = this.translationCache.get(`${this.fallbackLocale}:${key}`);
- }
-
- // If translation not found, return fallback string or key
- if (!value) {
- return fallback || key;
- }
-
+
+ const value = this.lookup(key);
+ if (!value) return fallback ?? key;
return this.replaceParams(value, actualParams);
}
- // Replace parameters in translation string
- private replaceParams(text: string, params?: { [key: string]: string | number }): string {
- if (!params) return text;
-
- logger.debug('Replacing parameters in translation text', 'I18nService', { textLength: text.length, paramCount: Object.keys(params).length });
-
- const result = text.replace(/\{(\w+)\}/g, (match, key) => {
- const paramValue = params[key];
- const replacement = paramValue?.toString() || match;
- return replacement;
- });
-
- logger.debug('Parameter replacement completed', 'I18nService', { originalLength: text.length, resultLength: result.length });
- return result;
+ async t(key: string, params?: Record): Promise {
+ return this.tSync(key, undefined, params);
}
- // ========================================
- // LOCALE MANAGEMENT
- // ========================================
+ // โโ Locale management โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async setLocale(locale: string): Promise {
- // Prevent rapid locale changes that could cause infinite loops
- if (this.localeChangeTimeout) {
- clearTimeout(this.localeChangeTimeout);
- }
-
- return new Promise((resolve) => {
- this.localeChangeTimeout = setTimeout(async () => {
- try {
- this.currentLocale = locale;
- localStorage.setItem('datapilot_locale', locale);
-
- // Clear translation cache to force reload with new locale
- this.translationCache.clear();
-
- // Re-initialize database translations for the new locale
- if (this.databaseInitialized) {
- await this.initializeDatabaseTranslations();
- }
-
- logger.debug('Locale changed and cache cleared', 'I18nService', { locale });
- resolve();
- } catch (error) {
- logger.error('Failed to set locale', 'I18nService', null, error as Error);
- resolve(); // Resolve anyway to prevent hanging
- }
- }, 500); // Debounce locale changes by 500ms
- });
+ const resolved = LOCALES[locale] ? locale : this.fallbackLocale;
+ this.currentLocale = resolved;
+ localStorage.setItem('datapilot_locale', resolved);
}
getCurrentLocale(): string {
@@ -300,272 +82,58 @@ class I18nService {
return this.fallbackLocale;
}
- // ========================================
- // UTILITY METHODS
- // ========================================
-
- isReady(): boolean {
- return this.databaseInitialized;
- }
-
- async waitForReady(): Promise {
- if (this.databaseInitialized) {
- return;
- }
-
- logger.debug(' Initializing translations for waitForReady...', 'I18nService');
-
- try {
- // Actually trigger the initialization instead of just waiting
- await this.initializeDatabaseTranslations();
- logger.debug(' Database initialization completed, translations ready', 'I18nService');
- } catch (error) {
- logger.error(' Failed to initialize translations in waitForReady', 'I18nService', null, error as Error);
- throw new Error('Database initialization timeout - translations not available');
- }
- }
+ // โโ Locale discovery (used by Settings UI) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- async getAvailableLocales(): Promise> {
- try {
- // Check if backend is available before making the call
- if (!apiService.isAvailable) {
- logger.debug(' Backend not available, returning fallback locales', 'I18nService');
- return [{ code: 'en', name: 'English' }];
- }
-
- const localeObjects = await apiService.getAvailableLocales();
- return localeObjects;
- } catch (error) {
- logger.warn(' Failed to get available locales, returning fallback', 'I18nService', { error: error instanceof Error ? error.message : String(error) });
- return [{ code: 'en', name: 'English' }];
- }
+ async getAvailableLocales(): Promise> {
+ return LOCALE_META;
}
- async getAvailableLocaleObjects(): Promise> {
- try {
- // Check if backend is available before making the call
- if (!apiService.isAvailable) {
- logger.debug(' Backend not available, returning fallback locale objects', 'I18nService');
- return [{ code: 'en', name: 'English' }];
- }
-
- const localeObjects = await apiService.getAvailableLocaleObjects();
-
- const locales = localeObjects.map(locale => ({
- code: locale.language_code,
- name: locale.language_name
- }));
-
- logger.debug(' Mapped active locales from API:', 'I18nService', { locales });
- return locales;
- } catch (error) {
- logger.warn(' Failed to get available locale objects, returning fallback', 'I18nService', { error: error instanceof Error ? error.message : String(error) });
- return [{ code: 'en', name: 'English' }];
- }
+ async getAvailableLocaleObjects(): Promise> {
+ return LOCALE_META;
}
- async getAvailableLanguageObjects(): Promise> {
- try {
- // Check if backend is available before making the call
- if (!apiService.isAvailable) {
- logger.debug(' Backend not available, returning fallback language objects', 'I18nService');
- return [{ code: 'en', name: 'English', uuid: '550e8400-e29b-41d4-a716-446655440000' }];
- }
-
- // Call the correct endpoint that returns full language objects (only active languages)
- const languageObjects = await apiService.getAllLanguagesWithFilter(true);
-
- const languages = languageObjects.map((lang: any) => ({
- code: lang.language_code,
- name: lang.language_name,
- uuid: lang.language_uuid
- }));
-
- logger.debug(' Mapped active languages from API:', 'I18nService', { languages });
- return languages;
- } catch (error) {
- logger.warn(' Failed to get available language objects, returning fallback', 'I18nService', { error: error instanceof Error ? error.message : String(error) });
- return [{ code: 'en', name: 'English', uuid: '550e8400-e29b-41d4-a716-446655440000' }];
- }
+ async getAvailableLanguageObjects(): Promise> {
+ return LOCALE_META.map((l, i) => ({ ...l, uuid: `local-${i}` }));
}
- async getLocaleData(): Promise {
- if (!this.databaseInitialized) {
- return [];
- }
+ // โโ Compatibility stubs (callers exist but no-op is fine now) โโโโโโโโโโโโ
- try {
- // Check if backend is available before making the call
- if (!apiService.isAvailable) {
- logger.debug(' Backend not available, returning empty locale data', 'I18nService');
- return [];
- }
-
- const localeObjects = await apiService.getAvailableLocaleObjects();
- const localeData: any[] = [];
-
- for (const localeObj of localeObjects) {
- const locale = localeObj.language_code;
- const translations = await apiService.getTranslationsByLocale(locale);
- const pages: Record> = {};
-
- for (const translation of translations) {
- try {
- const translationsData = JSON.parse(translation.translations_data || '{}');
- pages[translation.page_name] = translationsData;
- } catch (error) {
- logger.warn('Failed to parse translation data for locale', 'I18nService', {
- locale,
- pageName: translation.page_name,
- error: error instanceof Error ? error.message : String(error)
- });
- }
- }
-
- localeData.push({
- locale,
- name: localeObj.language_name,
- nativeName: localeObj.native_name,
- pages,
- lastUpdated: new Date()
- });
- }
-
- return localeData;
- } catch (error) {
- logger.warn('Failed to get locale data', 'I18nService', { error: error instanceof Error ? error.message : String(error) });
- return [];
- }
+ isReady(): boolean { return true; }
+ async waitForReady(): Promise {}
+ async forceReloadTranslations(): Promise {}
+ async refreshCache(): Promise {}
+ clearCache(): void {}
+ getCacheStats(): { totalKeys: number; totalLocales: number } {
+ return { totalKeys: Object.keys(LOCALES[this.currentLocale] ?? {}).length, totalLocales: Object.keys(LOCALES).length };
}
-
- // ========================================
- // CACHE UTILITIES
- // ========================================
-
getTranslationFromCache(locale: string, key: string): string | null {
- const cacheKey = `${locale}:${key}`;
- return this.translationCache.get(cacheKey) || null;
+ return LOCALES[locale]?.[key] ?? null;
}
-
isTranslationCached(locale: string, key: string): boolean {
- return this.getTranslationFromCache(locale, key) !== null;
- }
-
- // Force reload all translations
- async forceReloadTranslations(): Promise {
- try {
- logger.debug(' Force reloading translations from Python backend...', 'I18nService');
- this.translationCache.clear();
- await this.initializeDatabaseTranslations();
- } catch (error) {
- logger.error('Failed to force reload translations', 'I18nService', null, error as Error);
- throw error;
- }
- }
-
- getCacheStats(): { totalKeys: number; totalLocales: number } {
- const locales = new Set();
- let totalKeys = 0;
-
- for (const cacheKey of this.translationCache.keys()) {
- const [locale] = cacheKey.split(':');
- locales.add(locale);
- totalKeys++;
- }
-
- return {
- totalKeys,
- totalLocales: locales.size
- };
- }
-
- clearCache(): void {
- this.translationCache.clear();
- }
-
- async refreshCache(): Promise {
- if (this.databaseInitialized) {
- // Clear local cache
- this.clearCache();
-
- // Force reload from Python backend
- await this.forceReloadTranslations();
-
- logger.debug(' Translation cache refreshed from database', 'I18nService');
- }
- }
-
- // Auto-reload translations if missing (with safeguards against infinite loops)
- private autoReloadIfMissing(key: string): void {
- // Use a simple in-memory set to track reload attempts
- if (!this.reloadAttempts) {
- this.reloadAttempts = new Set();
- }
-
- // Only reload if we haven't tried this key recently and we haven't exceeded max attempts
- const maxReloadAttempts = 2; // Reduced from 3 to 2
- if (!this.reloadAttempts.has(key) && this.reloadAttempts.size < maxReloadAttempts) {
- this.reloadAttempts.add(key);
-
- // Debounce the reload to prevent multiple rapid reloads
- if (this.reloadTimeout) {
- clearTimeout(this.reloadTimeout);
- }
-
- this.reloadTimeout = setTimeout(async () => {
- try {
- logger.debug(' Auto-reloading translations due to missing key', 'I18nService', { key, attempts: this.reloadAttempts?.size });
-
- // Only reload if we haven't already reloaded recently
- const now = Date.now();
- if (!this.lastReloadTime || (now - this.lastReloadTime) > 5000) { // 5 second cooldown
- this.lastReloadTime = now;
- await this.forceReloadTranslations();
-
- // Clear reload attempts after successful reload
- this.reloadAttempts?.clear();
- } else {
- logger.debug(' Skipping reload due to recent reload', 'I18nService', {
- timeSinceLastReload: now - this.lastReloadTime
- });
- }
- } catch (error) {
- logger.warn(' Failed to auto-reload translations', 'I18nService', { error: error instanceof Error ? error.message : String(error) });
- }
- }, 3000); // Increased from 2 to 3 seconds to prevent rapid loops
- } else if (this.reloadAttempts.size >= maxReloadAttempts) {
- logger.warn(' Maximum auto-reload attempts reached, stopping to prevent infinite loop', 'I18nService', {
- key,
- attempts: this.reloadAttempts.size,
- maxAttempts: maxReloadAttempts
- });
- }
+ return key in (LOCALES[locale] ?? {});
}
+ async getLocaleData(): Promise { return []; }
}
export const i18nService = I18nService.getInstance();
-// ========================================
-// REACT HOOK
-// ========================================
-
-export const useTranslation = () => {
- return {
- t: i18nService.t.bind(i18nService),
- tSync: i18nService.tSync.bind(i18nService),
- setLocale: i18nService.setLocale.bind(i18nService),
- getCurrentLocale: i18nService.getCurrentLocale.bind(i18nService),
- getAvailableLocales: i18nService.getAvailableLocales.bind(i18nService),
- getAvailableLocaleObjects: i18nService.getAvailableLocaleObjects.bind(i18nService),
- getAvailableLanguageObjects: i18nService.getAvailableLanguageObjects.bind(i18nService),
- getLocaleData: i18nService.getLocaleData.bind(i18nService),
- getTranslationFromCache: i18nService.getTranslationFromCache.bind(i18nService),
- isTranslationCached: i18nService.isTranslationCached.bind(i18nService),
- getCacheStats: i18nService.getCacheStats.bind(i18nService),
- clearCache: i18nService.clearCache.bind(i18nService),
- refreshCache: i18nService.refreshCache.bind(i18nService),
- forceReloadTranslations: i18nService.forceReloadTranslations.bind(i18nService),
- isReady: i18nService.isReady.bind(i18nService),
- waitForReady: i18nService.waitForReady.bind(i18nService)
- };
-};
+// โโ React hook (identical surface area) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+export const useTranslation = () => ({
+ t: i18nService.t.bind(i18nService),
+ tSync: i18nService.tSync.bind(i18nService),
+ setLocale: i18nService.setLocale.bind(i18nService),
+ getCurrentLocale: i18nService.getCurrentLocale.bind(i18nService),
+ getAvailableLocales: i18nService.getAvailableLocales.bind(i18nService),
+ getAvailableLocaleObjects: i18nService.getAvailableLocaleObjects.bind(i18nService),
+ getAvailableLanguageObjects: i18nService.getAvailableLanguageObjects.bind(i18nService),
+ getLocaleData: i18nService.getLocaleData.bind(i18nService),
+ getTranslationFromCache: i18nService.getTranslationFromCache.bind(i18nService),
+ isTranslationCached: i18nService.isTranslationCached.bind(i18nService),
+ getCacheStats: i18nService.getCacheStats.bind(i18nService),
+ clearCache: i18nService.clearCache.bind(i18nService),
+ refreshCache: i18nService.refreshCache.bind(i18nService),
+ forceReloadTranslations: i18nService.forceReloadTranslations.bind(i18nService),
+ isReady: i18nService.isReady.bind(i18nService),
+ waitForReady: i18nService.waitForReady.bind(i18nService),
+});
diff --git a/dashboard/src/services/NotificationService.ts b/dashboard/src/services/NotificationService.ts
index 4ddc1ae..0b62e9e 100644
--- a/dashboard/src/services/NotificationService.ts
+++ b/dashboard/src/services/NotificationService.ts
@@ -133,17 +133,35 @@ export class NotificationService {
public apiError(error: any, context?: string): void {
let title = i18nService.tSync('error.notifications.apiError.title', 'API Error');
let message = i18nService.tSync('error.notifications.apiError.generic', 'An error occurred while communicating with the server');
+ let details = '';
// Parse different types of API errors
if (error?.response?.data?.detail) {
const detail = error.response.data.detail;
-
+
if (typeof detail === 'object') {
// Handle structured error response
if (detail.message) {
message = i18nService.tSync(detail.message) || detail.message;
}
-
+
+ // Extract detailed error information - prioritize 'details' field
+ if (detail.details) {
+ // Try to translate if it looks like a translation key
+ if (typeof detail.details === 'string' && detail.details.includes('.')) {
+ details = i18nService.tSync(detail.details, detail.details);
+ } else {
+ details = detail.details;
+ }
+ }
+ // If no details field, try to use service_status_code info or other fields
+ else if (detail.service_name && !details) {
+ // For external service errors, try to construct a helpful message
+ if (detail.service_name === 'Salesforce') {
+ details = 'Failed to establish Salesforce connection. Please verify your credentials and try again.';
+ }
+ }
+
// Handle field errors (these contain the actual error details)
if (detail.field_errors && typeof detail.field_errors === 'object') {
const fieldErrors = Object.values(detail.field_errors);
@@ -161,7 +179,7 @@ export class NotificationService {
}
}
}
-
+
// Set specific title based on error type
if (detail.error_code) {
switch (detail.error_code) {
@@ -177,6 +195,9 @@ export class NotificationService {
case 'connection_error':
title = i18nService.tSync('error.notifications.apiError.connection', 'Connection Error');
break;
+ case 'external_service_error':
+ title = i18nService.tSync('error.notifications.apiError.server', 'Server Error');
+ break;
}
}
} else if (typeof detail === 'string') {
@@ -195,9 +216,12 @@ export class NotificationService {
title = `${title} - ${context}`;
}
+ // Combine message and details
+ const fullMessage = details ? `${message}\n\n${details}` : message;
+
this.error({
title,
- message: this.wrapMessage(message),
+ message: this.wrapMessage(fullMessage),
autoClose: 6000, // Longer for errors
});
}
diff --git a/dashboard/src/services/SObjectCacheService.ts b/dashboard/src/services/SObjectCacheService.ts
index 54fe690..c8b8904 100644
--- a/dashboard/src/services/SObjectCacheService.ts
+++ b/dashboard/src/services/SObjectCacheService.ts
@@ -239,7 +239,7 @@ export class SObjectCacheService {
logger.debug('Cache miss for describeGlobal, fetching from API', 'SObjectCacheService', { cacheKey });
try {
- const sobjectNames = await SalesforceService.getSObjectList();
+ const sobjectNames = await SalesforceService.getSObjectList(connUuid);
// Convert to the expected format
const sobjects = sobjectNames.map(name => ({ name }));
diff --git a/datapilot.code-workspace b/datapilot.code-workspace
new file mode 100644
index 0000000..bbc5b6f
--- /dev/null
+++ b/datapilot.code-workspace
@@ -0,0 +1,371 @@
+{
+ "folders": [
+
+ {
+ "name": "backend",
+ "path": "./backend"
+ },
+ {
+ "name": "dashboard",
+ "path": "./dashboard"
+ },
+ {
+ "name": "docker",
+ "path": "./docker"
+ },
+ {
+ "name": "docs",
+ "path": "./docs"
+ }
+ ],
+ "settings": {
+ // Python settings
+ "python.defaultInterpreterPath": "${workspaceFolder:Backend}/.venv/bin/python",
+ "python.terminal.activateEnvironment": true,
+ "python.terminal.activateEnvInCurrentTerminal": true,
+ "python.venvPath": "${workspaceFolder:Backend}",
+ "python.venvFolders": [
+ ".venv"
+ ],
+ "python.analysis.typeCheckingMode": "basic",
+ "python.analysis.autoImportCompletions": true,
+ "python.analysis.diagnosticMode": "workspace",
+ "python.formatting.provider": "black",
+ "python.linting.enabled": true,
+ "python.linting.pylintEnabled": false,
+ "python.linting.ruffEnabled": true,
+ "python.linting.ruffPath": "${workspaceFolder:Backend}/.venv/bin/ruff",
+ "[python]": {
+ "editor.defaultFormatter": "ms-python.black-formatter",
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.organizeImports": "explicit"
+ },
+ "editor.rulers": [120]
+ },
+
+ // TypeScript/JavaScript settings
+ "typescript.tsdk": "${workspaceFolder:Dashboard}/node_modules/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true,
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit",
+ "source.organizeImports": "explicit"
+ }
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit",
+ "source.organizeImports": "explicit"
+ }
+ },
+ "[javascript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[json]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+
+ // General editor settings
+ "editor.tabSize": 2,
+ "editor.insertSpaces": true,
+ "editor.trimAutoWhitespace": true,
+ "files.trimTrailingWhitespace": true,
+ "files.insertFinalNewline": true,
+ "files.exclude": {
+ "**/__pycache__": true,
+ "**/*.pyc": true,
+ "**/.pytest_cache": true,
+ "**/.mypy_cache": true,
+ "**/.ruff_cache": true,
+ "**/node_modules": true,
+ "**/.venv": true,
+ "**/dist": true,
+ "**/build": true,
+ "**/*.egg-info": true
+ },
+ "search.exclude": {
+ "**/node_modules": true,
+ "**/__pycache__": true,
+ "**/.venv": true,
+ "**/dist": true,
+ "**/build": true,
+ "**/*.egg-info": true,
+ "**/logs": true
+ },
+
+ // File associations
+ "files.associations": {
+ "*.env.example": "properties",
+ "*.properties": "properties"
+ },
+
+ // Terminal settings
+ "terminal.integrated.cwd": "${workspaceFolder}",
+ "terminal.integrated.defaultProfile.osx": "zsh",
+ "terminal.integrated.shellIntegration.enabled": true,
+ // Note: Python extension handles venv activation automatically via python.terminal.activateEnvironment
+
+ // Git settings
+ "git.ignoreLimitWarning": true,
+ "git.autofetch": true,
+
+ // Language-specific settings
+ "[markdown]": {
+ "editor.wordWrap": "on",
+ "editor.quickSuggestions": {
+ "comments": "off",
+ "strings": "off",
+ "other": "off"
+ }
+ },
+ "[yaml]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[dockerfile]": {
+ "editor.defaultFormatter": "ms-azuretools.vscode-docker"
+ }
+ },
+ "extensions": {
+ "recommendations": [
+ // Python
+ "ms-python.python",
+ "ms-python.vscode-pylance",
+ "ms-python.black-formatter",
+ "charliermarsh.ruff",
+
+ // TypeScript/JavaScript
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode",
+
+ // Docker
+ "ms-azuretools.vscode-docker",
+
+ // Git
+ "eamodio.gitlens",
+
+ // General
+ "ms-vscode.vscode-json",
+ "redhat.vscode-yaml",
+ "ms-vscode.makefile-tools",
+
+ // Markdown
+ "yzhang.markdown-all-in-one",
+
+ // MongoDB
+ "mongodb.mongodb-vscode"
+ ]
+ },
+ "tasks": {
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Backend: Install Dependencies",
+ "type": "shell",
+ "command": "cd backend && python -m pip install -e .",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Backend: Run Server",
+ "type": "shell",
+ "command": "cd backend && python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Backend: Run Tests",
+ "type": "shell",
+ "command": "cd backend && python -m pytest",
+ "group": "test",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Backend: Format Code",
+ "type": "shell",
+ "command": "cd backend && black .",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Backend: Lint Code",
+ "type": "shell",
+ "command": "cd backend && ruff check .",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Dashboard: Install Dependencies",
+ "type": "shell",
+ "command": "cd dashboard && npm install",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Dashboard: Run Dev Server",
+ "type": "shell",
+ "command": "cd dashboard && npm run dev",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Dashboard: Build",
+ "type": "shell",
+ "command": "cd dashboard && npm run build",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Dashboard: Lint",
+ "type": "shell",
+ "command": "cd dashboard && npm run lint",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Docker: Start Services",
+ "type": "shell",
+ "command": "cd docker && docker-compose up -d",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Docker: Stop Services",
+ "type": "shell",
+ "command": "cd docker && docker-compose down",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Docker: View Logs",
+ "type": "shell",
+ "command": "cd docker && docker-compose logs -f",
+ "group": "build",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ }
+ ]
+ },
+ "launch": {
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: FastAPI Backend",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "uvicorn",
+ "args": [
+ "app.main:app",
+ "--reload",
+ "--host",
+ "0.0.0.0",
+ "--port",
+ "8000"
+ ],
+ "jinja": true,
+ "justMyCode": false,
+ "cwd": "${workspaceFolder:Backend}",
+ "env": {
+ "PYTHONPATH": "${workspaceFolder:Backend}"
+ },
+ "python": "${workspaceFolder:Backend}/.venv/bin/python",
+ "console": "integratedTerminal"
+ },
+ {
+ "name": "Python: Current File",
+ "type": "debugpy",
+ "request": "launch",
+ "program": "${file}",
+ "console": "integratedTerminal",
+ "cwd": "${workspaceFolder:Backend}",
+ "env": {
+ "PYTHONPATH": "${workspaceFolder:Backend}"
+ },
+ "python": "${workspaceFolder:Backend}/.venv/bin/python"
+ },
+ {
+ "name": "Python: Pytest",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "pytest",
+ "args": [
+ "${file}",
+ "-v"
+ ],
+ "console": "integratedTerminal",
+ "cwd": "${workspaceFolder:Backend}",
+ "env": {
+ "PYTHONPATH": "${workspaceFolder:Backend}"
+ },
+ "python": "${workspaceFolder:Backend}/.venv/bin/python"
+ }
+ ],
+ "compounds": [
+ {
+ "name": "Full Stack: Backend + Frontend",
+ "configurations": [
+ "Python: FastAPI Backend"
+ ],
+ "preLaunchTask": "Dashboard: Run Dev Server"
+ }
+ ]
+ }
+}
+
diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend
index 40143ab..375cff4 100644
--- a/docker/Dockerfile.backend
+++ b/docker/Dockerfile.backend
@@ -40,5 +40,5 @@ EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/api/v1/health || exit 1
-# Run the application
-CMD ["uv", "run", "python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
+# Run the application using the dedicated runner script
+CMD ["uv", "run", "python", "run_server.py", "--prod", "--workers", "4", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 325c480..dfe7c60 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -13,7 +13,7 @@ services:
MONGO_INITDB_USERNAME: datapilot
MONGO_INITDB_PASSWORD: datapilot123
ports:
- - "27018:27017"
+ - "27918:27017"
volumes:
- mongodb_data:/data/db
- ./mongodb-init:/docker-entrypoint-initdb.d
@@ -70,7 +70,15 @@ services:
networks:
- datapilot-network
healthcheck:
- test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/api/v1/health"]
+ test:
+ [
+ "CMD",
+ "wget",
+ "--no-verbose",
+ "--tries=1",
+ "--spider",
+ "http://localhost/api/v1/health",
+ ]
interval: 30s
timeout: 10s
retries: 3
diff --git a/docker/environment-configs/backend.env b/docker/environment-configs/backend.env
index 5f47b66..b491a16 100644
--- a/docker/environment-configs/backend.env
+++ b/docker/environment-configs/backend.env
@@ -79,14 +79,14 @@ METRICS_PORT=9090
# DEVELOPMENT SETTINGS
# ============================================================================
-ENABLE_SWAGGER_UI=false
+ENABLE_SWAGGER_UI=true
ENABLE_RELOAD=false
# ============================================================================
# LLM Settings
# ============================================================================
LLM_PROVIDER=openai # openai or "groq" or "ollama"
-LLM_MODEL_NAME=gpt-4o-mini # gpt-4o-mini , llama-3.3-70b-versatile, qwen3:30b or any model name supported by your provider
+LLM_MODEL_NAME=gpt-5-mini
LLM_API_KEY=your API LLM KEY
LLM_TEMPERATURE=0.7
LLM_SUMMERY_TEMPERATURE=0.4
diff --git a/docker/environment-configs/dashboard.env b/docker/environment-configs/dashboard.env
index d53398f..6b218fd 100644
--- a/docker/environment-configs/dashboard.env
+++ b/docker/environment-configs/dashboard.env
@@ -5,10 +5,10 @@
# API CONFIGURATION (Docker overrides)
# ============================================================================
-# Backend API base URL - points to backend service in Docker
-# For Docker: use internal service name (backend:8000)
-# For external access: use localhost:8001
-VITE_API_BASE_URL=http://localhost:8001
+# Backend API base URL. Leave unset (default) to auto-detect: the dashboard will call the
+# backend on whatever host served the page, port 8001. Only set this explicitly if the
+# frontend and backend are on different hosts/domains (split-host deployment).
+#VITE_API_BASE_URL=http://your-backend-host:8001
# API timeout in milliseconds
VITE_API_TIMEOUT=30000
diff --git a/docs/05-apex-code-management.md b/docs/05-apex-code-management.md
index 0327b28..fef4f8f 100644
--- a/docs/05-apex-code-management.md
+++ b/docs/05-apex-code-management.md
@@ -3,65 +3,46 @@
## Table of Contents
- [Overview](#overview)
-- [Planned Key Capabilities](#planned-key-capabilities)
- - [Advanced Apex Execution](#advanced-apex-execution-partially-implemented)
- - [Development Tools](#development-tools-under-development)
- - [Testing and Debugging](#testing-and-debugging-planned)
-- [Current Implementation Status](#current-implementation-status)
- - [Backend API](#backend-api-partially-implemented)
- - [Frontend UI](#frontend-ui-under-development)
+- [Key Capabilities](#key-capabilities)
+ - [Apex Execution](#apex-execution)
+ - [Saved Apex & Debug Levels](#saved-apex--debug-levels)
+ - [Testing](#testing)
+- [Implementation Status](#implementation-status)
+ - [Backend API](#backend-api)
+ - [Frontend UI](#frontend-ui)
+- [Planned](#planned)
## Overview
-**๐ง Feature Under Development** - DataPilot's Apex Code Management system is currently under active development. Both the backend API infrastructure and frontend UI components are partially implemented and still being built. This documentation outlines our planned comprehensive Apex development capabilities with advanced code execution, compilation, testing, and debugging features.
+DataPilot's Apex Code Management gives you a Monaco-powered Apex editor backed by a full set of execution, compilation, and testing endpoints against your connected Salesforce org.
-**Current Status:**
-- ๐ง **Backend API**: Partially implemented - basic anonymous Apex execution and REST endpoints exist but need completion
-- ๐ง **Frontend UI**: Under development - ApexTab component exists but needs completion
-- ๐ **Planned Features**: Advanced code editor, testing framework, debugging tools, and collaboration features
+## Key Capabilities
-## Planned Key Capabilities
+### Apex Execution
+- **Anonymous Apex Execution**: Run Apex code against your org via the Salesforce Tooling API and view results/debug output
+- **Apex REST Integration**: Execute custom Apex REST endpoints
+- **Package & Trigger Compilation**: Compile Apex packages and triggers, and browse existing classes/triggers
-### Advanced Apex Execution (Partially Implemented)
-- **Anonymous Apex Execution**: ๐ง Basic implementation - Run Apex code directly in Salesforce
-- **Apex REST Integration**: ๐ง Basic implementation - Execute custom REST endpoints
-- **Code Compilation**: ๐ Planned - Package and trigger compilation
-- **Test Execution**: ๐ Planned - Apex test coverage and debugging
-- **Debug Level Management**: ๐ Planned - Advanced debugging and logging
+### Saved Apex & Debug Levels
+- **Saved Apex Snippets**: Create, update, delete, and favorite reusable Apex snippets
+- **Debug Level Configuration**: Configure, validate, and inspect debug levels per saved snippet, with a dedicated debug-levels info endpoint
-### Development Tools (Under Development)
-- **Code Editor**: ๐ง In Progress - Advanced Apex code editor with syntax highlighting
-- **Code Templates**: ๐ Planned - Pre-built Apex code templates and snippets
-- **Version Control**: ๐ Planned - Track code changes and iterations
-- **Collaborative Development**: ๐ Planned - Team-based code development
+### Testing
+- **Test Execution**: Run Apex tests and view results
+- **Compile-and-Test Workflow**: Combined compile + test execution in a single call
-### Testing and Debugging (Planned)
-- **Test Execution**: ๐ Planned - Run Apex tests with coverage reporting
-- **Debug Logging**: ๐ Planned - Advanced debugging and log analysis
-- **Performance Monitoring**: ๐ Planned - Code execution performance tracking
-- **Error Analysis**: ๐ Planned - Comprehensive error detection and resolution
+## Implementation Status
-## Current Implementation Status
-
-### Backend API (Partially Implemented)
+### Backend API
```
Code Input โ Salesforce Tooling API โ Execution โ Result Processing โ Response
```
+- Anonymous execution, REST integration, package/trigger compilation, test execution, and saved-Apex CRUD (including debug levels) are all implemented โ see `backend/app/api/v1/endpoints/salesforce.py` and `backend/app/api/v1/endpoints/saved_apex.py`.
-**Partially Implemented Components:**
-- **Apex Executor**: ๐ง Basic anonymous Apex code execution via Salesforce Tooling API
-- **REST Integrator**: ๐ง Basic Apex REST endpoint execution with request/response handling
-- **Limits Monitor**: ๐ Planned - Apex governor limits tracking and monitoring
-- **Error Handling**: ๐ง Basic error detection and reporting
-
-### Frontend UI (Under Development)
-**Current State:**
-- **ApexTab Component**: Basic structure exists but needs completion
-- **Code Editor**: Textarea placeholder - needs Monaco Editor integration
-- **Execution Interface**: UI framework ready but needs API integration
-- **Result Display**: Modal structure exists but needs result processing
-
----
-
-*This documentation outlines the planned Apex Code Management feature. The backend API infrastructure is complete, but the frontend UI is under development. This feature will provide comprehensive Apex development capabilities with advanced code execution, compilation, testing, and debugging features.*
+### Frontend UI
+- **ApexTab**: Monaco Editor-based code editor, wired to execution/compile/test/saved-Apex endpoints (`dashboard/src/components/query-editor/ApexTab.tsx`)
+## Planned
+- **Apex Governor Limits Monitoring**: Surface governor limit usage during execution
+- **Code Templates**: Pre-built Apex snippets library
+- **Version History**: Track changes to saved Apex snippets over time