Coverage for app/venv/lib/python3.14/site-packages/weblate/addons/cleanup.py: 40%
61 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from __future__ import annotations
7from typing import TYPE_CHECKING, ClassVar
9from django.utils.translation import gettext_lazy
11from weblate.addons.base import UpdateBaseAddon
12from weblate.addons.events import AddonEvent
13from weblate.formats.base import TranslationFormat
14from weblate.trans.exceptions import FileParseError
16if TYPE_CHECKING: 16 ↛ 17line 16 didn't jump to line 17 because the condition on line 16 was never true
17 from weblate.auth.models import User
18 from weblate.trans.models import Component
21class BaseCleanupAddon(UpdateBaseAddon):
22 @staticmethod
23 def can_install_format(component: Component) -> bool:
24 return component.file_format_cls.can_delete_unit
26 @classmethod
27 def can_install(cls, component: Component, user: User | None) -> bool:
28 if not component.has_template() or not cls.can_install_format(component):
29 return False
30 return super().can_install(component, user)
33class CleanupAddon(BaseCleanupAddon):
34 name = "weblate.cleanup.generic"
35 verbose = gettext_lazy("Cleanup translation files")
36 description = gettext_lazy(
37 "Update all translation files to match the monolingual base file. "
38 "For most file formats, this means removing stale translation keys "
39 "no longer present in the base file."
40 )
41 icon = "eraser.svg"
42 events: ClassVar[set[AddonEvent]] = {
43 AddonEvent.EVENT_PRE_COMMIT,
44 AddonEvent.EVENT_POST_UPDATE,
45 }
47 @classmethod
48 def can_install_format(cls, component: Component) -> bool:
49 return (
50 super().can_install_format(component)
51 or component.file_format_cls.cleanup_unused
52 != TranslationFormat.cleanup_unused
53 )
55 def update_translations(self, component, previous_head) -> None:
56 for translation in self.iterate_translations(component):
57 filenames = translation.store.cleanup_unused()
58 if filenames is None:
59 continue
60 self.extra_files.extend(filenames)
61 # Do not update hash here as this is just before parsing updated files
63 def pre_commit(
64 self,
65 translation,
66 author: str,
67 store_hash: bool,
68 activity_log_id: int | None = None,
69 ) -> None:
70 if translation.is_source and not translation.component.intermediate:
71 return
72 try:
73 filenames = translation.store.cleanup_unused()
74 except FileParseError:
75 return
76 if filenames is not None:
77 self.extra_files.extend(filenames)
78 if store_hash:
79 translation.store_hash()
82class RemoveBlankAddon(BaseCleanupAddon):
83 name = "weblate.cleanup.blank"
84 verbose = gettext_lazy("Remove blank strings")
85 description = gettext_lazy(
86 "Removes strings without a translation from translation files."
87 )
88 events: ClassVar[set[AddonEvent]] = {
89 AddonEvent.EVENT_POST_COMMIT,
90 AddonEvent.EVENT_POST_UPDATE,
91 }
92 icon = "eraser.svg"
94 def update_translations(self, component: Component, previous_head: str) -> None:
95 for translation in self.iterate_translations(component):
96 filenames = translation.store.cleanup_blank()
97 if filenames is None:
98 continue
99 self.extra_files.extend(filenames)
100 # Do not update hash in post_update, only in post_commit
101 if previous_head == "weblate:post-commit":
102 translation.store_hash()
104 def post_commit(
105 self, component: Component, store_hash: bool, activity_log_id: int | None = None
106 ) -> None:
107 self.post_update(
108 component,
109 "weblate:post-commit" if store_hash else "weblate:post-commit-no-store",
110 skip_push=True,
111 )