import json import copy import re from typing import Dict, Any, Optional from flask import session from common.extensions import cache_manager from common.utils.business_event import BusinessEvent from common.utils.business_event_context import current_event class TranslationServices: @staticmethod def translate_config(tenant_id: int, config_data: Dict[str, Any], field_config: str, target_language: str, source_language: Optional[str] = None, context: Optional[str] = None) -> Dict[str, Any]: """ Vertaalt een configuratie op basis van een veld-configuratie. Args: tenant_id: Identificatie van de tenant waarvoor we de vertaling doen. config_data: Een dictionary of JSON (die dan wordt geconverteerd naar een dictionary) met configuratiegegevens field_config: De naam van een veld-configuratie (bijv. 'fields') target_language: De taal waarnaar vertaald moet worden source_language: Optioneel, de brontaal van de configuratie context: Optioneel, een specifieke context voor de vertaling Returns: Een dictionary met de vertaalde configuratie """ config_type = config_data.get('type', 'Unknown') config_version = config_data.get('version', 'Unknown') span_name = f"{config_type}-{config_version}-{field_config}" if current_event: with current_event.create_span(span_name): translated_config = TranslationServices._translate_config(tenant_id, config_data, field_config, target_language, source_language, context) return translated_config else: with BusinessEvent('Config Translation Service', tenant_id): with current_event.create_span(span_name): translated_config = TranslationServices._translate_config(tenant_id, config_data, field_config, target_language, source_language, context) return translated_config @staticmethod def _translate_config(tenant_id: int, config_data: Dict[str, Any], field_config: str, target_language: str, source_language: Optional[str] = None, context: Optional[str] = None) -> Dict[str, Any]: # Zorg ervoor dat we een dictionary hebben if isinstance(config_data, str): config_data = json.loads(config_data) # 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') config_version = config_data.get('version', 'Unknown') if field_config in config_data: fields = config_data[field_config] # Haal description uit metadata voor context als geen context is opgegeven description_context = "" 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 (alleen strings) if 'name' in field_data and is_nonempty_str(field_data['name']): field_context = context if context else description_context t = safe_translate(field_data['name'], field_context) if t: translated_config[field_config][field_name]['name'] = t if 'title' in field_data and is_nonempty_str(field_data.get('title')): field_context = context if context else description_context 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 is_nonempty_str(field_data.get('description')): field_context = context if context else description_context 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 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 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']: 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 def translate(tenant_id: int, text: str, target_language: str, source_language: Optional[str] = None, context: Optional[str] = None)-> str: if current_event: with current_event.create_span('Translation'): translation_cache = cache_manager.translation_cache.get_translation(text, target_language, source_language, context) return translation_cache.translated_text else: with BusinessEvent('Translation Service', tenant_id): with current_event.create_span('Translation'): translation_cache = cache_manager.translation_cache.get_translation(text, target_language, source_language, context) return translation_cache.translated_text