From 2b04692fab332759caf714d6acd870739a753a1d Mon Sep 17 00:00:00 2001 From: Josako Date: Mon, 15 Sep 2025 17:57:13 +0200 Subject: [PATCH 1/4] - TRA-76 - Send Button color changes implemented - TRA-72 - Translation of privacy statement and T&C - TRA-73 - Strange characters in Tenant Make Name - Addition of meta information in Specialist Form Fields --- common/services/utils/translation_services.py | 157 ++++++++++++------ .../translation_with_context/1.0.0.yaml | 7 +- .../translation_without_context/1.0.0.yaml | 7 +- .../MINIMAL_PERSONAL_CONTACT_FORM/1.0.0.yaml | 5 + .../assets/vue-components/ConsentRichText.vue | 94 +++++++++++ .../assets/vue-components/FormField.vue | 80 +++++---- eveai_chat_client/templates/chat.html | 20 +-- eveai_chat_client/templates/scripts.html | 18 +- 8 files changed, 280 insertions(+), 108 deletions(-) create mode 100644 eveai_chat_client/static/assets/vue-components/ConsentRichText.vue diff --git a/common/services/utils/translation_services.py b/common/services/utils/translation_services.py index 4f0e273..437ebba 100644 --- a/common/services/utils/translation_services.py +++ b/common/services/utils/translation_services.py @@ -1,4 +1,6 @@ import json +import copy +import re from typing import Dict, Any, Optional from flask import session @@ -50,8 +52,8 @@ class TranslationServices: if isinstance(config_data, str): config_data = json.loads(config_data) - # Maak een kopie van de originele data om te wijzigen - translated_config = config_data.copy() + # Maak een deep copy van de originele data om te wijzigen en input-mutatie te vermijden + translated_config = copy.deepcopy(config_data) # Haal type en versie op voor de Business Event span config_type = config_data.get('type', 'Unknown') @@ -65,71 +67,124 @@ class TranslationServices: if not context and 'metadata' in config_data and 'description' in config_data['metadata']: description_context = config_data['metadata']['description'] + # Hulpfuncties + def is_nonempty_str(val: Any) -> bool: + return isinstance(val, str) and val.strip() != '' + + def safe_translate(text: str, ctx: Optional[str]): + try: + res = cache_manager.translation_cache.get_translation( + text=text, + target_lang=target_language, + source_lang=source_language, + context=ctx + ) + return res.translated_text if res else None + except Exception as e: + if current_event: + current_event.log_error('translation_error', { + 'tenant_id': tenant_id, + 'config_type': config_type, + 'config_version': config_version, + 'field_config': field_config, + 'error': str(e) + }) + return None + + tag_pair_pattern = re.compile(r'<([a-zA-Z][\w-]*)>[\s\S]*?<\/\1>') + + def extract_tag_counts(text: str) -> Dict[str, int]: + counts: Dict[str, int] = {} + for m in tag_pair_pattern.finditer(text or ''): + tag = m.group(1) + counts[tag] = counts.get(tag, 0) + 1 + return counts + + def tags_valid(source: str, translated: str) -> bool: + return extract_tag_counts(source) == extract_tag_counts(translated) + + # Counters + meta_consentRich_translated_count = 0 + meta_aria_translated_count = 0 + meta_inline_tags_invalid_after_translation_count = 0 + # Loop door elk veld in de configuratie for field_name, field_data in fields.items(): - # Vertaal name als het bestaat en niet leeg is - if 'name' in field_data and field_data['name']: - # Gebruik context indien opgegeven, anders description_context + # Vertaal name als het bestaat en niet leeg is (alleen strings) + if 'name' in field_data and is_nonempty_str(field_data['name']): field_context = context if context else description_context - translated_name = cache_manager.translation_cache.get_translation( - text=field_data['name'], - target_lang=target_language, - source_lang=source_language, - context=field_context - ) - if translated_name: - translated_config[field_config][field_name]['name'] = translated_name.translated_text + t = safe_translate(field_data['name'], field_context) + if t: + translated_config[field_config][field_name]['name'] = t - if 'title' in field_data and field_data['title']: - # Gebruik context indien opgegeven, anders description_context + if 'title' in field_data and is_nonempty_str(field_data.get('title')): field_context = context if context else description_context - translated_title = cache_manager.translation_cache.get_translation( - text=field_data['title'], - target_lang=target_language, - source_lang=source_language, - context=field_context - ) - if translated_title: - translated_config[field_config][field_name]['title'] = translated_title.translated_text + t = safe_translate(field_data['title'], field_context) + if t: + translated_config[field_config][field_name]['title'] = t # Vertaal description als het bestaat en niet leeg is - if 'description' in field_data and field_data['description']: - # Gebruik context indien opgegeven, anders description_context + if 'description' in field_data and is_nonempty_str(field_data.get('description')): field_context = context if context else description_context - translated_desc = cache_manager.translation_cache.get_translation( - text=field_data['description'], - target_lang=target_language, - source_lang=source_language, - context=field_context - ) - if translated_desc: - translated_config[field_config][field_name]['description'] = translated_desc.translated_text + t = safe_translate(field_data['description'], field_context) + if t: + translated_config[field_config][field_name]['description'] = t # Vertaal context als het bestaat en niet leeg is - if 'context' in field_data and field_data['context']: - translated_ctx = cache_manager.translation_cache.get_translation( - text=field_data['context'], - target_lang=target_language, - source_lang=source_language, - context=context - ) - if translated_ctx: - translated_config[field_config][field_name]['context'] = translated_ctx.translated_text + if 'context' in field_data and is_nonempty_str(field_data.get('context')): + t = safe_translate(field_data['context'], context) + if t: + translated_config[field_config][field_name]['context'] = t - # vertaal allowed values als het veld bestaat en de waarden niet leeg zijn. - if 'allowed_values' in field_data and field_data['allowed_values']: + # vertaal allowed_values als het veld bestaat en waarden niet leeg zijn (alleen string-items) + if 'allowed_values' in field_data and isinstance(field_data['allowed_values'], list) and field_data['allowed_values']: translated_allowed_values = [] for allowed_value in field_data['allowed_values']: - translated_allowed_value = cache_manager.translation_cache.get_translation( - text=allowed_value, - target_lang=target_language, - source_lang=source_language, - context=context - ) - translated_allowed_values.append(translated_allowed_value.translated_text) + if is_nonempty_str(allowed_value): + t = safe_translate(allowed_value, context) + translated_allowed_values.append(t if t else allowed_value) + else: + translated_allowed_values.append(allowed_value) if translated_allowed_values: translated_config[field_config][field_name]['allowed_values'] = translated_allowed_values + # Vertaal meta.consentRich en meta.aria* + meta = field_data.get('meta') + if isinstance(meta, dict): + # consentRich + if is_nonempty_str(meta.get('consentRich')): + consent_ctx = (context if context else description_context) or '' + consent_ctx = f"Consent rich text with inline tags. Keep tag names intact and translate only inner text. {consent_ctx}".strip() + t = safe_translate(meta['consentRich'], consent_ctx) + if t and tags_valid(meta['consentRich'], t): + translated_config[field_config][field_name].setdefault('meta', {})['consentRich'] = t + meta_consentRich_translated_count += 1 + else: + if t and not tags_valid(meta['consentRich'], t) and current_event: + src_counts = extract_tag_counts(meta['consentRich']) + dst_counts = extract_tag_counts(t) + current_event.log_error('inline_tags_validation_failed', { + 'tenant_id': tenant_id, + 'config_type': config_type, + 'config_version': config_version, + 'field_config': field_config, + 'field_name': field_name, + 'target_language': target_language, + 'source_tag_counts': src_counts, + 'translated_tag_counts': dst_counts + }) + meta_inline_tags_invalid_after_translation_count += 1 + # fallback: keep original (already in deep copy) + # aria* + for k, v in list(meta.items()): + if isinstance(k, str) and k.startswith('aria') and is_nonempty_str(v): + aria_ctx = (context if context else description_context) or '' + aria_ctx = f"ARIA label for accessibility. Short, imperative, descriptive. Form '{config_type} {config_version}', field '{field_name}'. {aria_ctx}".strip() + t2 = safe_translate(v, aria_ctx) + if t2: + translated_config[field_config][field_name].setdefault('meta', {})[k] = t2 + meta_aria_translated_count += 1 + return translated_config @staticmethod diff --git a/config/prompts/globals/translation_with_context/1.0.0.yaml b/config/prompts/globals/translation_with_context/1.0.0.yaml index ec01348..a05eb85 100644 --- a/config/prompts/globals/translation_with_context/1.0.0.yaml +++ b/config/prompts/globals/translation_with_context/1.0.0.yaml @@ -9,8 +9,11 @@ content: > '{context}' - Do not translate text in between double square brackets, as these are names or terms that need to remain intact. - Remove the triple quotes in your translation! + These are best practices you should follow: + + - Do not translate text in between double square brackets, as these are names or terms that need to remain intact. Remove the square brackets in the translation! + - We use inline tags (Custom HTML/XML-like tags). Ensure the tags themself are not translated and remain intact in the translation. The text inbetween the tags should be translated. e.g. "Terms & Conditions" translates in Dutch to Gebruiksvoorwaarden + - Remove the triple quotes in your translation! I only want you to return the translation. No explanation, no options. I need to be able to directly use your answer without further interpretation. If more than one option is available, present me with the most probable one. diff --git a/config/prompts/globals/translation_without_context/1.0.0.yaml b/config/prompts/globals/translation_without_context/1.0.0.yaml index c895031..95477f9 100644 --- a/config/prompts/globals/translation_without_context/1.0.0.yaml +++ b/config/prompts/globals/translation_without_context/1.0.0.yaml @@ -6,8 +6,11 @@ content: > into '{target_language}'. - Do not translate text in between double square brackets, as these are names or terms that need to remain intact. - Remove the triple quotes in your translation! + These are best practices you should follow: + + - Do not translate text in between double square brackets, as these are names or terms that need to remain intact. Remove the square brackets in the translation! + - We use inline tags (Custom HTML/XML-like tags). Ensure the tags themself are not translated and remain intact in the translation. The text inbetween the tags should be translated. e.g. "Terms & Conditions" translates in Dutch to Gebruiksvoorwaarden + - Remove the triple quotes in your translation! I only want you to return the translation. No explanation, no options. I need to be able to directly use your answer without further interpretation. If more than one option is available, present me with the most probable one. diff --git a/config/specialist_forms/globals/MINIMAL_PERSONAL_CONTACT_FORM/1.0.0.yaml b/config/specialist_forms/globals/MINIMAL_PERSONAL_CONTACT_FORM/1.0.0.yaml index 0b62127..b238615 100644 --- a/config/specialist_forms/globals/MINIMAL_PERSONAL_CONTACT_FORM/1.0.0.yaml +++ b/config/specialist_forms/globals/MINIMAL_PERSONAL_CONTACT_FORM/1.0.0.yaml @@ -24,6 +24,11 @@ fields: type: "boolean" description: "Consent" required: true + meta: + kind: "consent" + consentRich: "Ik Agree with the Terms and Conditions and the Privacy Statement of Ask Eve AI" + ariaPrivacy: "Open privacyverklaring in a modal dialog" + ariaTerms: "Open algemene voorwaarden in a modal dialog" metadata: author: "Josako" date_added: "2025-07-29" diff --git a/eveai_chat_client/static/assets/vue-components/ConsentRichText.vue b/eveai_chat_client/static/assets/vue-components/ConsentRichText.vue new file mode 100644 index 0000000..c083d4a --- /dev/null +++ b/eveai_chat_client/static/assets/vue-components/ConsentRichText.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/eveai_chat_client/static/assets/vue-components/FormField.vue b/eveai_chat_client/static/assets/vue-components/FormField.vue index 336de27..4a2b130 100644 --- a/eveai_chat_client/static/assets/vue-components/FormField.vue +++ b/eveai_chat_client/static/assets/vue-components/FormField.vue @@ -104,14 +104,16 @@ > {{ field.name }} - - - {{ texts.consentPrefix }} - {{ texts.privacyLink }} - {{ texts.consentMiddle }} - {{ texts.termsLink }} - {{ texts.consentSuffix }} - + + * @@ -180,9 +182,11 @@