- 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
203 lines
11 KiB
Python
203 lines
11 KiB
Python
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 |