- verbeteringen client

- Enkel nog probleem met vertaling van de ProgressTracker constanten
This commit is contained in:
Josako
2025-07-21 21:45:46 +02:00
parent 0f33beddf4
commit 4ad621428e
16 changed files with 982 additions and 378 deletions

View File

@@ -8,7 +8,7 @@ import { ref, computed, onMounted } from 'vue';
*/
export function useTranslation() {
const isTranslationReady = ref(false);
const currentLanguage = ref('nl');
const currentLanguage = ref('en');
const isTranslating = ref(false);
const lastError = ref(null);
@@ -139,7 +139,7 @@ export function useTranslation() {
* Get current language from chatConfig or fallback
*/
const getCurrentLanguage = () => {
return window.chatConfig?.language || currentLanguage.value || 'nl';
return window.chatConfig?.language || currentLanguage.value || 'en';
};
/**
@@ -215,6 +215,8 @@ export function useConstantsTranslation() {
CONSTANTS_CACHE.translations = translated;
console.log('useConstantsTranslation: Successfully translated and cached constants');
console.log('useConstantsTranslation: Current language:', CONSTANTS_CACHE.currentLanguage);
console.log('useConstantsTranslation: Cached translations:', CONSTANTS_CACHE.translations);
return translated;
} catch (error) {
console.error('useConstantsTranslation: Error translating constants:', error);

View File

@@ -1,238 +0,0 @@
// eveai_chat_client/static/assets/js/composables/useTranslation.js
import { ref, computed, onMounted } from 'vue';
/**
* Vue 3 Composable for translation management
* Provides modern alternative to window.TranslationClient
*/
export function useTranslation() {
const isTranslationReady = ref(false);
const currentLanguage = ref('nl');
const isTranslating = ref(false);
const lastError = ref(null);
// Check if translation system is available with retry mechanism
const checkTranslationReady = () => {
if (window.TranslationClient && typeof window.TranslationClient.translate === 'function') {
isTranslationReady.value = true;
return true;
}
return false;
};
onMounted(() => {
// Initial check
if (checkTranslationReady()) {
return;
}
// Retry mechanism - wait for TranslationClient to become available
let retryCount = 0;
const maxRetries = 10;
const retryInterval = 100; // 100ms
const retryCheck = () => {
if (checkTranslationReady()) {
return; // Success!
}
retryCount++;
if (retryCount < maxRetries) {
setTimeout(retryCheck, retryInterval);
} else {
console.warn('TranslationClient is not available after retries');
isTranslationReady.value = false;
}
};
// Start retry process
setTimeout(retryCheck, retryInterval);
});
/**
* Translate text to target language
* @param {string} text - Text to translate
* @param {string} targetLang - Target language code
* @param {string|null} sourceLang - Source language code (optional)
* @param {string|null} context - Translation context (optional)
* @param {string} apiPrefix - API prefix for tenant routing
* @returns {Promise<object>} Translation result
*/
const translate = async (text, targetLang, sourceLang = null, context = null, apiPrefix = '') => {
if (!isTranslationReady.value || !window.TranslationClient) {
const error = new Error('Translation system not ready');
lastError.value = error;
throw error;
}
if (!text || !text.trim()) {
const error = new Error('No text provided for translation');
lastError.value = error;
throw error;
}
isTranslating.value = true;
lastError.value = null;
try {
const result = await window.TranslationClient.translate(
text,
targetLang,
sourceLang,
context,
apiPrefix
);
// Update current language if translation was successful
if (result.success) {
currentLanguage.value = targetLang;
}
return result;
} catch (error) {
console.error('Translation error in composable:', error);
lastError.value = error;
throw error;
} finally {
isTranslating.value = false;
}
};
/**
* Translate text with automatic error handling and loading state
* @param {string} text - Text to translate
* @param {string} targetLang - Target language code
* @param {Object} options - Translation options
* @returns {Promise<string|null>} Translated text or null on error
*/
const translateSafe = async (text, targetLang, options = {}) => {
const {
sourceLang = null,
context = null,
apiPrefix = '',
fallbackText = text
} = options;
try {
const result = await translate(text, targetLang, sourceLang, context, apiPrefix);
return result.success ? result.translated_text : fallbackText;
} catch (error) {
console.warn('Safe translation failed, using fallback:', error.message);
return fallbackText;
}
};
/**
* Batch translate multiple texts
* @param {Array<string>} texts - Array of texts to translate
* @param {string} targetLang - Target language code
* @param {Object} options - Translation options
* @returns {Promise<Array<string>>} Array of translated texts
*/
const translateBatch = async (texts, targetLang, options = {}) => {
const results = await Promise.allSettled(
texts.map(text => translateSafe(text, targetLang, options))
);
return results.map((result, index) =>
result.status === 'fulfilled' ? result.value : texts[index]
);
};
/**
* Get current language from chatConfig or fallback
*/
const getCurrentLanguage = () => {
return window.chatConfig?.language || currentLanguage.value;
};
/**
* Get API prefix from chatConfig or fallback
*/
const getApiPrefix = () => {
return window.chatConfig?.apiPrefix || '';
};
return {
// State
isTranslationReady,
currentLanguage: computed(() => getCurrentLanguage()),
isTranslating,
lastError,
// Methods
translate,
translateSafe,
translateBatch,
// Utilities
getCurrentLanguage,
getApiPrefix
};
}
/**
* Simplified composable for basic translation needs
* Use this when you only need simple text translation
*/
export function useTranslationClient() {
const { translate, translateSafe, isTranslationReady, isTranslating, lastError } = useTranslation();
return {
translate,
translateSafe,
isTranslationReady,
isTranslating,
lastError
};
}
/**
* Composable for reactive text translation
* Automatically translates text when language changes
*/
export function useReactiveTranslation(text, options = {}) {
const { translateSafe, currentLanguage } = useTranslation();
const translatedText = ref(text);
const isLoading = ref(false);
const {
context = null,
sourceLang = null,
autoTranslate = true
} = options;
// Watch for language changes and auto-translate
if (autoTranslate) {
// We'll implement this when we have proper reactivity setup
// For now, provide manual translation method
}
const updateTranslation = async (newLanguage = null) => {
const targetLang = newLanguage || currentLanguage.value;
if (!text || targetLang === sourceLang) {
translatedText.value = text;
return;
}
isLoading.value = true;
try {
const result = await translateSafe(text, targetLang, {
sourceLang,
context,
apiPrefix: window.chatConfig?.apiPrefix || ''
});
translatedText.value = result;
} finally {
isLoading.value = false;
}
};
return {
translatedText,
isLoading,
updateTranslation
};
}

View File

@@ -0,0 +1,181 @@
// LanguageProvider.js - Central language management system
import { ref, reactive, computed, provide, inject } from 'vue';
import { useConstantsTranslation } from '../composables/useTranslation.js';
// Injection key for type safety
export const LANGUAGE_PROVIDER_KEY = Symbol('LanguageProvider');
/**
* Language Provider Service
* Central management of language state and translations
*/
export function createLanguageProvider(initialLanguage = 'en', apiPrefix = '') {
// Reactive state
const currentLanguage = ref(initialLanguage);
const isTranslating = ref(false);
const translationError = ref(null);
// Translation composable
const { translateConstants, getCachedTranslations, clearCache } = useConstantsTranslation();
// Component-specific translations cache
const componentTranslations = reactive({});
/**
* Register a component for translations with component-specific caching
*/
const registerComponent = (componentName, originalTexts) => {
console.log(`LanguageProvider: Registering component ${componentName} with language ${currentLanguage.value}`);
if (!componentTranslations[componentName]) {
componentTranslations[componentName] = reactive({
original: originalTexts,
translated: { ...originalTexts }, // Start with original English texts
isLoading: false,
error: null
});
// Force initial translation if current language is not English
if (currentLanguage.value !== 'en') {
console.log(`LanguageProvider: Component ${componentName} needs initial translation to ${currentLanguage.value}`);
translateComponentTexts(componentName, currentLanguage.value);
}
}
return componentTranslations[componentName];
};
/**
* Translate texts for a specific component
*/
const translateComponentTexts = async (componentName, targetLanguage) => {
const component = componentTranslations[componentName];
if (!component) {
console.warn(`LanguageProvider: Component ${componentName} not found for translation`);
return;
}
component.isLoading = true;
component.error = null;
try {
if (targetLanguage === 'en') {
// For English, use original texts (no translation needed)
component.translated = { ...component.original };
console.log(`LanguageProvider: Using original English texts for ${componentName}`);
} else {
// For other languages, translate from English
console.log(`LanguageProvider: Translating ${componentName} from English to ${targetLanguage}`);
const translatedTexts = await translateConstants(
component.original,
targetLanguage,
{
context: componentName,
apiPrefix
}
);
component.translated = translatedTexts;
console.log(`LanguageProvider: Successfully translated ${componentName} to ${targetLanguage}`);
}
} catch (error) {
console.error(`LanguageProvider: Translation error for ${componentName}:`, error);
component.error = error;
// Fallback to original English texts
component.translated = { ...component.original };
} finally {
component.isLoading = false;
}
};
/**
* Update language for all registered components
*/
const setLanguage = async (newLanguage) => {
if (currentLanguage.value === newLanguage) {
return;
}
console.log('LanguageProvider: Setting language to', newLanguage);
currentLanguage.value = newLanguage;
isTranslating.value = true;
translationError.value = null;
try {
// Update all registered components
const translationPromises = Object.keys(componentTranslations).map(componentName =>
translateComponentTexts(componentName, newLanguage)
);
await Promise.all(translationPromises);
console.log(`LanguageProvider: Successfully updated all components to ${newLanguage}`);
} catch (error) {
console.error('LanguageProvider: Error setting language:', error);
translationError.value = error;
} finally {
isTranslating.value = false;
}
};
/**
* Get translations voor een specifieke component
*/
const getComponentTranslations = (componentName) => {
return componentTranslations[componentName] || null;
};
/**
* Clear alle caches
*/
const clearAllCaches = () => {
clearCache();
Object.keys(componentTranslations).forEach(key => {
delete componentTranslations[key];
});
};
return {
// State
currentLanguage: computed(() => currentLanguage.value),
isTranslating: computed(() => isTranslating.value),
translationError: computed(() => translationError.value),
// Methods
registerComponent,
setLanguage,
getComponentTranslations,
clearAllCaches,
// Computed
componentTranslations: computed(() => componentTranslations)
};
}
/**
* Composable voor het gebruiken van de Language Provider
*/
export function useLanguageProvider() {
const provider = inject(LANGUAGE_PROVIDER_KEY);
if (!provider) {
throw new Error('useLanguageProvider must be used within a LanguageProvider');
}
return provider;
}
/**
* Composable voor component-specifieke vertalingen
*/
export function useComponentTranslations(componentName, originalTexts) {
const provider = useLanguageProvider();
// Registreer component bij eerste gebruik
const translations = provider.registerComponent(componentName, originalTexts);
return {
translations: computed(() => translations.translated),
isLoading: computed(() => translations.isLoading),
error: computed(() => translations.error),
currentLanguage: provider.currentLanguage
};
}

View File

@@ -43,6 +43,10 @@ import ProgressTracker from './ProgressTracker.vue';
import LanguageSelector from './LanguageSelector.vue';
import ChatInput from './ChatInput.vue';
// Import language provider
import { createLanguageProvider, LANGUAGE_PROVIDER_KEY } from '../js/services/LanguageProvider.js';
import { provide } from 'vue';
export default {
name: 'ChatApp',
components: {
@@ -54,12 +58,28 @@ export default {
ProgressTracker,
ChatInput
},
setup() {
// Haal initiële taal uit chatConfig
const initialLanguage = window.chatConfig?.language || 'nl';
const apiPrefix = window.chatConfig?.apiPrefix || '';
// Creëer language provider
const languageProvider = createLanguageProvider(initialLanguage, apiPrefix);
// Provide aan alle child components
provide(LANGUAGE_PROVIDER_KEY, languageProvider);
return {
languageProvider
};
},
data() {
// Maak een lokale kopie van de chatConfig om undefined errors te voorkomen
const chatConfig = window.chatConfig || {};
const settings = chatConfig.settings || {};
const initialLanguage = chatConfig.language || 'nl';
const initialLanguage = chatConfig.language || 'en';
const originalExplanation = chatConfig.explanation || '';
const tenantMake = chatConfig.tenantMake || {};
@@ -331,7 +351,7 @@ export default {
// Add a placeholder AI message that will be updated by the progress tracker
const placeholderMessage = {
id: this.messageIdCounter++,
content: 'Bezig met verwerken...',
// content: 'Bezig met verwerken...',
sender: 'ai',
type: 'text',
timestamp: new Date().toISOString(),

View File

@@ -97,7 +97,7 @@
{{ message.content }}
</div>
<button v-if="message.retryable" @click="$emit('retry-message', message.id)" class="retry-btn">
Opnieuw proberen
{{ messageTexts.retry }}
</button>
</div>
</template>
@@ -116,6 +116,7 @@
import DynamicForm from './DynamicForm.vue';
import ProgressTracker from './ProgressTracker.vue';
import { useIconManager } from '../js/composables/useIconManager.js';
import { useComponentTranslations } from '../js/services/LanguageProvider.js';
export default {
name: 'ChatMessage',
@@ -129,7 +130,25 @@ export default {
// Watch message.formData.icon for automatic icon loading
watchIcon(() => props.message.formData?.icon);
return {};
// Use component translations from provider (English as base language)
const originalTexts = {
retry: 'Try again',
copy: 'Copy',
timestamp: 'Timestamp',
errorMessage: 'An error occurred while processing your request.'
};
const { translations, isLoading, error, currentLanguage } = useComponentTranslations(
'chat_message',
originalTexts
);
return {
messageTexts: translations,
translationLoading: isLoading,
translationError: error,
currentLanguage
};
},
props: {
message: {
@@ -163,12 +182,10 @@ export default {
}
},
mounted() {
// Luister naar taalwijzigingen
document.addEventListener('language-changed', this.handleLanguageChange);
// Component initialization if needed
},
beforeUnmount() {
// Verwijder event listener bij verwijderen component
document.removeEventListener('language-changed', this.handleLanguageChange);
// Component cleanup if needed
},
computed: {
hasFormData() {
@@ -214,22 +231,17 @@ export default {
return hasActualValues;
},
async handleLanguageChange(event) {
// Controleer of dit het eerste bericht is in een gesprek met maar één bericht
// Dit wordt al afgehandeld door MessageHistory component, dus we hoeven hier niets te doen
// De implementatie hiervan blijft in MessageHistory om dubbele vertaling te voorkomen
},
handleSpecialistError(eventData) {
console.log('ChatMessage received specialist-error event:', eventData);
// Creëer een error message met correcte styling
// Create an error message with correct styling
this.message.type = 'error';
this.message.content = eventData.message || 'Er is een fout opgetreden bij het verwerken van uw verzoek.';
this.message.content = eventData.message || this.messageTexts.errorMessage;
this.message.retryable = true;
this.message.error = true; // Voeg error flag toe voor styling
this.message.error = true; // Add error flag for styling
// Bubble up naar parent component voor verdere afhandeling
// Bubble up to parent component for further handling
this.$emit('specialist-error', {
messageId: this.message.id,
...eventData

View File

@@ -21,12 +21,26 @@
</template>
<script>
import { useLanguageProvider } from '../js/services/LanguageProvider.js';
export default {
name: 'LanguageSelector',
setup() {
// Optionally use provider for reactive current language
try {
const provider = useLanguageProvider();
return {
providerLanguage: provider.currentLanguage
};
} catch {
// Provider not available, use props
return {};
}
},
props: {
initialLanguage: {
type: String,
default: 'nl'
default: 'en'
},
currentLanguage: {
type: String,
@@ -53,11 +67,17 @@ export default {
this.$emit('language-changed', this.selectedLanguage);
// DOM event
const event = new CustomEvent('vue:language-changed', {
const event = new CustomEvent('language-changed', {
detail: { language: this.selectedLanguage }
});
document.dispatchEvent(event);
},
computed: {
// Use provider language if available, otherwise use props
effectiveCurrentLanguage() {
return this.providerLanguage || this.currentLanguage || this.initialLanguage;
}
},
methods: {
getAvailableLanguages() {
const languages = [];
@@ -109,7 +129,7 @@ export default {
this.$emit('language-changed', languageCode);
// DOM event
const event = new CustomEvent('vue:language-changed', {
const event = new CustomEvent('language-changed', {
detail: { language: languageCode }
});
document.dispatchEvent(event);

View File

@@ -53,10 +53,34 @@
</template>
<script>
import { useTranslationClient, useConstantsTranslation } from '../js/composables/useTranslation.js';
import { useComponentTranslations } from '../js/services/LanguageProvider.js';
export default {
name: 'ProgressTracker',
setup() {
// Define original English texts (base language for developers)
const originalTexts = {
error: 'Error while processing',
completed: 'Processing completed',
processing: 'Processing...'
};
// Gebruik component translations
const { translations, isLoading, error, currentLanguage } = useComponentTranslations(
'progress_tracker',
originalTexts
);
console.log('Translations:', translations);
console.log('Current language:', currentLanguage);
return {
statusTexts: translations,
translationLoading: isLoading,
translationError: error,
currentLanguage
};
},
props: {
taskId: {
type: String,
@@ -76,99 +100,56 @@ export default {
connecting: true,
error: null,
progressLines: [],
eventSource: null,
// Vertaalde status teksten
translatedStatusTexts: {
error: 'Error while processing',
completed: 'Processing completed',
processing: 'Processing...'
},
currentLanguage: 'nl'
eventSource: null
};
},
computed: {
isProcessing() {
return !this.isCompleted && !this.hasError && !this.connecting;
},
// Computed properties voor vertaalde status teksten
// Computed properties voor vertaalde status teksten uit provider
errorText() {
return this.translatedStatusTexts.error;
return this.statusTexts.error;
},
completedText() {
return this.translatedStatusTexts.completed;
return this.statusTexts.completed;
},
processingText() {
return this.translatedStatusTexts.processing;
return this.statusTexts.processing;
}
},
created() {
// Create named handler for language changes
this.languageChangeHandler = (event) => {
if (event.detail && event.detail.language) {
console.log('ProgressTracker: Received language change event:', event.detail.language);
// The LanguageProvider will automatically handle the translation update
// We just need to ensure the component is aware of the change
this.handleLanguageChange(event);
}
};
// Listen for language changes
document.addEventListener('language-changed', this.languageChangeHandler);
},
mounted() {
this.connectToProgressStream();
// Setup translation composables
const { translateSafe } = useTranslationClient();
const { translateConstants, getCachedTranslations, getCachedLanguage } = useConstantsTranslation();
this.translateSafe = translateSafe;
this.translateConstants = translateConstants;
this.getCachedTranslations = getCachedTranslations;
this.getCachedLanguage = getCachedLanguage;
// Check if we already have cached translations and apply them
const cachedTranslations = this.getCachedTranslations();
if (cachedTranslations) {
console.log('ProgressTracker: Applying cached translations on mount');
this.translatedStatusTexts = { ...cachedTranslations };
this.currentLanguage = this.getCachedLanguage();
}
// Luister naar taalwijzigingen
this.languageChangeHandler = (event) => {
if (event.detail && event.detail.language) {
this.handleLanguageChange(event.detail.language);
}
};
document.addEventListener('language-changed', this.languageChangeHandler);
},
beforeUnmount() {
this.disconnectEventSource();
// Cleanup language change listener
// Remove language change event listener
if (this.languageChangeHandler) {
document.removeEventListener('language-changed', this.languageChangeHandler);
}
},
methods: {
async handleLanguageChange(newLanguage) {
console.log('ProgressTracker: Language change to', newLanguage);
// Skip if same language
if (this.currentLanguage === newLanguage) {
return;
}
this.currentLanguage = newLanguage;
// Define the original Dutch constants
const originalTexts = {
error: 'Fout bij verwerking',
completed: 'Verwerking voltooid',
processing: 'Bezig met redeneren...'
};
try {
// Use global constants translation with caching
const translatedTexts = await this.translateConstants(originalTexts, newLanguage, {
context: 'progress_tracker',
apiPrefix: this.apiPrefix
});
// Update component state with translated texts
this.translatedStatusTexts = translatedTexts;
console.log('ProgressTracker: Successfully updated status texts for', newLanguage);
} catch (error) {
console.error('ProgressTracker: Error translating status texts:', error);
// Fallback to original Dutch texts
this.translatedStatusTexts = originalTexts;
handleLanguageChange(event) {
if (event.detail && event.detail.language) {
console.log(`ProgressTracker: Language changed to ${event.detail.language}`);
// The LanguageProvider automatically updates translations through reactive system
// Force component update to ensure UI reflects the new translations
this.$forceUpdate();
}
},
@@ -205,7 +186,7 @@ export default {
} catch (error) {
console.error('Failed to create EventSource:', error);
this.error = 'Kan geen verbinding maken met de voortgangsstream.';
this.error = 'Unable to connect to the server SSE stream.';
this.connecting = false;
}
},
@@ -343,7 +324,7 @@ export default {
});
} catch (error) {
console.error('Error parsing specialist error data:', error);
this.error = 'Er is een onbekende fout opgetreden.';
this.error = 'An unknown error occurred while processing your request.';
this.isCompleted = true;
this.hasError = true;
this.connecting = false;
@@ -351,7 +332,7 @@ export default {
// Emit generic error
this.$emit('specialist-error', {
message: 'Er is een onbekende fout opgetreden.',
message: 'An unknown error occurred while processing your request.',
originalError: 'Failed to parse error data',
taskId: this.taskId
});
@@ -360,7 +341,7 @@ export default {
handleError(event) {
console.error('SSE Error event:', event);
this.error = 'Er is een fout opgetreden bij het verwerken van updates.';
this.error = 'An unknown error occurred while connecting to the server. Please try again later.';
this.connecting = false;
// Try to parse error data

View File

@@ -49,7 +49,7 @@ const props = defineProps({
},
initialLanguage: {
type: String,
default: 'nl'
default: 'en'
},
supportedLanguageDetails: {
type: Object,

View File

@@ -17,7 +17,7 @@
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { useTranslationClient } from '../js/composables/useTranslation.js';
import { useComponentTranslations } from '../js/services/LanguageProvider.js';
const props = defineProps({
originalText: {
@@ -26,7 +26,7 @@ const props = defineProps({
},
currentLanguage: {
type: String,
default: 'nl'
default: 'en'
},
apiPrefix: {
type: String,
@@ -34,9 +34,17 @@ const props = defineProps({
}
});
const { translateSafe } = useTranslationClient();
const translatedText = ref(props.originalText);
const isLoading = ref(false);
// Use component translations from provider
const originalTexts = computed(() => ({
explanation: props.originalText || ''
}));
const { translations, isLoading, error, currentLanguage } = useComponentTranslations(
'sidebar_explanation',
originalTexts.value
);
const translatedText = computed(() => translations.value.explanation || props.originalText);
// Render markdown content
const renderedExplanation = computed(() => {
@@ -52,41 +60,12 @@ const renderedExplanation = computed(() => {
}
});
// Watch for language changes
watch(() => props.currentLanguage, async (newLanguage) => {
await updateTranslation(newLanguage);
});
// Watch for text changes
watch(() => props.originalText, async () => {
await updateTranslation(props.currentLanguage);
});
const updateTranslation = async (targetLanguage) => {
if (!props.originalText || targetLanguage === 'nl') {
translatedText.value = props.originalText;
return;
}
isLoading.value = true;
try {
const result = await translateSafe(props.originalText, targetLanguage, {
context: 'sidebar_explanation',
apiPrefix: props.apiPrefix,
fallbackText: props.originalText
});
translatedText.value = result;
} catch (error) {
console.warn('Sidebar explanation translation failed:', error);
translatedText.value = props.originalText;
} finally {
isLoading.value = false;
}
};
onMounted(() => {
updateTranslation(props.currentLanguage);
// Watch for text changes to update the provider
watch(() => props.originalText, () => {
// Update original texts when prop changes
originalTexts.value = {
explanation: props.originalText || ''
};
});
</script>