From f80e0b678453cd7f1ef5a185a7d60a3ef2fc61d8 Mon Sep 17 00:00:00 2001 From: martino <32328813+f3rs3n@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:58:55 +0200 Subject: [PATCH] fix(monitor): honor guest terminology and leaf-scoped Slovak fallback --- .../test_notification_outcome_wording.py | 35 +++++++++++++++++-- AppImage/messages/es/common.json | 2 +- AppImage/scripts/notification_templates.py | 18 ++++++---- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/.github/scripts/tests/test_notification_outcome_wording.py b/.github/scripts/tests/test_notification_outcome_wording.py index 0bf932dd..f841da7b 100644 --- a/.github/scripts/tests/test_notification_outcome_wording.py +++ b/.github/scripts/tests/test_notification_outcome_wording.py @@ -88,7 +88,38 @@ class OutcomeWording(unittest.TestCase): self.assertIn('Post-restore tasks completed',restored['body']) self.assertNotIn('úplne pripravený',restored['body']) - def test_spanish_restore_names_vms_and_containers_not_invitados(self): + def test_slovak_fallback_is_per_stale_leaf_not_unrelated_key_presence(self): + import importlib.util + spec = importlib.util.spec_from_file_location('isolated_slovak_future', SCRIPTS / 'notification_templates.py') + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + upstream = json.loads((ROOT / 'AppImage/messages/sk/common.json').read_text())['runtime']['notifications'] + english = self.catalog['runtime']['notifications'] + data = {'hostname': 'node-a', 'category': 'temperature', 'reason': 'old', + 'duration': '3d', 'original_severity': 'WARNING', 'guests': 3} + for event, field in (('error_resolved', 'title'), ('error_resolved', 'body'), + ('system_restore_completed', 'body'), + ('backup_complete', 'title'), ('backup_complete', 'body')): + # A new translation must work independently of another family's key. + translated = copy.deepcopy(upstream) + translated['templates'][event][field] = 'REVIEWED TRANSLATION {hostname}' + with self.subTest(event=event, field=field, case='future translation'): + with patch.object(module, '_load_runtime_catalog', side_effect=lambda lang: translated if lang == 'sk' else english): + result = module.render_template(event, data, 'sk') + self.assertEqual(result[field], 'REVIEWED TRANSLATION node-a') + # Adding outcome keys must not re-enable unrelated stale claims. + stale = copy.deepcopy(upstream) + stale.setdefault('backup', {})['unconfirmedBody'] = 'REVIEWED OUTCOME' + with self.subTest(event=event, field=field, case='stale after key addition'): + with patch.object(module, '_load_runtime_catalog', side_effect=lambda lang: stale if lang == 'sk' else english): + result = module.render_template(event, data, 'sk') + self.assertEqual(result[field], module.render_template(event, data, 'en')[field]) + # The unchanged restore title is not an unsafe readiness claim. + self.assertEqual(module.render_template('system_restore_completed', data, 'sk')['title'], + upstream['templates']['system_restore_completed']['title'].format(**data)) + + def test_spanish_restore_uses_maintainer_guests_terminology(self): import importlib.util spec = importlib.util.spec_from_file_location('isolated_spanish_restore', SCRIPTS / 'notification_templates.py') assert spec is not None and spec.loader is not None @@ -97,7 +128,7 @@ class OutcomeWording(unittest.TestCase): result = module.render_template('system_restore_completed', { 'hostname':'node-a','guests':3,'stubs':0,'stale_nodes':0, 'components':1,'duration':'2m','warnings_block':''}, 'es') - self.assertIn('máquinas virtuales y contenedores', result['body']) + self.assertIn('Configuraciones de guests aplicadas: 3', result['body']) self.assertNotIn('invitados', result['body'].lower()) def test_settings_labels_stay_at_upstream_values_in_all_locales(self): diff --git a/AppImage/messages/es/common.json b/AppImage/messages/es/common.json index 43cc5f24..e25e0a1a 100644 --- a/AppImage/messages/es/common.json +++ b/AppImage/messages/es/common.json @@ -6557,7 +6557,7 @@ }, "system_restore_completed": { "title": "{hostname}: restauración del host finalizada", - "body": "Tareas posteriores a la restauración completadas en segundo plano.\n\nConfiguraciones de máquinas virtuales y contenedores aplicadas: {guests}\nDirectorios auxiliares de montajes bind: {stubs}\nDirectorios de nodos obsoletos eliminados: {stale_nodes}\nComponentes reinstalados: {components}\nDuración: {duration}\n{warnings_block}", + "body": "Tareas posteriores a la restauración completadas en segundo plano.\n\nConfiguraciones de guests aplicadas: {guests}\nDirectorios auxiliares de montajes bind: {stubs}\nDirectorios de nodos obsoletos eliminados: {stale_nodes}\nComponentes reinstalados: {components}\nDuración: {duration}\n{warnings_block}", "label": "Restauración del host completada" }, "system_problem": { diff --git a/AppImage/scripts/notification_templates.py b/AppImage/scripts/notification_templates.py index 5ea8d925..eba823a9 100644 --- a/AppImage/scripts/notification_templates.py +++ b/AppImage/scripts/notification_templates.py @@ -1856,12 +1856,18 @@ def render_template(event_type: str, data: Dict[str, Any], _catalog_value(requested_catalog, key) or _catalog_value(english_catalog, key) ) - # A catalog without the outcome keys predates this report contract. - # Keep its Settings labels, but do not render old recovery, restore - # or backup success claims (e.g. the exact upstream Slovak catalog). - if (event_type in ('backup_complete', 'error_resolved', 'system_restore_completed') - and field in ('title', 'body') - and not _catalog_value(requested_catalog, 'backup.unconfirmedBody')): + # Keep the Slovak catalog with its maintainer. Suppress only the + # exact stale report leaves, not future translations or safe titles. + # An unrelated outcome key cannot version recovery/restore wording. + stale_slovak_reports = { + 'templates.backup_complete.title': '{hostname} → {storage}: Záloha dokončená — {vmname} ({vmid})', + 'templates.backup_complete.body': 'Záloha {vmname} (ID: {vmid}) na úložisku {storage} bola úspešne dokončená.\nVeľkosť: {size}', + 'templates.error_resolved.title': '{hostname}: Vyriešené - {category}{entity_suffix}', + 'templates.error_resolved.body': 'Problém v kategórii {category} bol vyriešený.\n{reason}\n🚦 Predchádzajúca závažnosť: {original_severity}\n⏱️ Trvanie: {duration}', + 'templates.system_restore_completed.body': 'Úlohy po obnove boli dokončené na pozadí.\n\nPoužité VM a LXC: {guests}\nZástupné priečinky bind mountov: {stubs}\nOdstránené zastarané priečinky uzlov: {stale_nodes}\nPreinštalované súčasti: {components}\nTrvanie: {duration}\n{warnings_block}\nUzol je teraz úplne pripravený na použitie.', + } + if (requested_language == 'sk' and key in stale_slovak_reports + and localized == stale_slovak_reports[key]): localized = _catalog_value(english_catalog, key) if localized: template[field] = localized