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

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7from typing import TYPE_CHECKING, ClassVar 

8 

9from django.utils.translation import gettext_lazy 

10 

11from weblate.addons.base import UpdateBaseAddon 

12from weblate.addons.events import AddonEvent 

13from weblate.formats.base import TranslationFormat 

14from weblate.trans.exceptions import FileParseError 

15 

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 

19 

20 

21class BaseCleanupAddon(UpdateBaseAddon): 

22 @staticmethod 

23 def can_install_format(component: Component) -> bool: 

24 return component.file_format_cls.can_delete_unit 

25 

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) 

31 

32 

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 } 

46 

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 ) 

54 

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 

62 

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() 

80 

81 

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" 

93 

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() 

103 

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 )