Coverage for app/venv/lib/python3.14/site-packages/weblate/addons/properties.py: 21%

89 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 

5""" 

6Properties cleanup addon. 

7 

8This is reimplementation of 

9https://github.com/freeplane/freeplane/blob/1.4.x/freeplane_ant/ 

10src/main/java/org/freeplane/ant/FormatTranslation.java 

11""" 

12 

13from __future__ import annotations 

14 

15import re 

16from typing import TYPE_CHECKING, ClassVar 

17 

18from django.utils.translation import gettext_lazy 

19 

20from weblate.addons.base import BaseAddon 

21from weblate.addons.events import AddonEvent 

22from weblate.addons.forms import PropertiesSortAddonForm 

23 

24if TYPE_CHECKING: 24 ↛ 25line 24 didn't jump to line 25 because the condition on line 24 was never true

25 from weblate.addons.base import CompatDict 

26 

27SPLITTER = re.compile(r"\s*=\s*") 

28UNICODE = re.compile(r"\\[uU][0-9a-fA-F]{4}") 

29 

30 

31def sort_key(line: str, case_sensitive: bool) -> str: 

32 """Sort key for properties.""" 

33 prefix = SPLITTER.split(line, 1)[0] 

34 if case_sensitive: 

35 return prefix 

36 return prefix.lower() 

37 

38 

39def unicode_format(match) -> str: 

40 """ 

41 Format unicode characters. 

42 

43 Callback for re.sub. 

44 """ 

45 return f"\\u{match.group(0)[2:].upper()}" 

46 

47 

48def fix_newlines(lines) -> None: 

49 """Convert newlines to unix.""" 

50 for i, line in enumerate(lines): 

51 if line.endswith("\r\n"): 

52 lines[i] = line[:-2] + "\n" 

53 elif line.endswith("\r"): 

54 lines[i] = line[:-1] + "\n" 

55 

56 

57def format_unicode(lines) -> None: 

58 """Format unicode characters.""" 

59 for i, line in enumerate(lines): 

60 if UNICODE.findall(line) is None: 

61 continue 

62 lines[i] = UNICODE.sub(unicode_format, line) 

63 

64 

65def value_quality(value) -> int: 

66 """Calculate value quality.""" 

67 if not value: 

68 return 0 

69 if "[translate me]" in value: 

70 return 1 

71 if "[auto]" in value: 

72 return 2 

73 return 3 

74 

75 

76def filter_lines(lines: list[str]) -> list[str]: 

77 """Filter comments, empty lines and duplicate strings.""" 

78 result: list[str] = [] 

79 lastkey = None 

80 lastvalue = None 

81 

82 for line in lines: 

83 # Skip comments and blank lines 

84 if line[0] == "#" or not line.strip(): 

85 continue 

86 parts = SPLITTER.split(line, 1) 

87 

88 # Missing = or empty key 

89 if len(parts) != 2 or not parts[0]: 

90 continue 

91 

92 key, value = parts 

93 # Strip trailing \n in value 

94 value = value[:-1] 

95 

96 # Empty translation 

97 if value in {"", "[auto]", "[translate me]"}: 

98 continue 

99 

100 # Check for duplicate key 

101 if key == lastkey: 

102 # Skip duplicate 

103 if value == lastvalue: 

104 continue 

105 

106 quality = value_quality(value) 

107 lastquality = value_quality(lastvalue) 

108 

109 if quality > lastquality: 

110 # Replace lower quality with new one 

111 result.pop() 

112 elif lastquality > quality or quality < 4: 

113 # Drop lower quality one 

114 continue 

115 

116 result.append(line) 

117 lastkey = key 

118 lastvalue = value 

119 

120 return result 

121 

122 

123def format_file(filename: str, case_sensitive: bool) -> bool: 

124 """Format single properties file.""" 

125 with open(filename) as handle: 

126 lines = handle.readlines() 

127 

128 result = sorted(lines, key=lambda line: sort_key(line, case_sensitive)) 

129 

130 fix_newlines(result) 

131 format_unicode(result) 

132 result = filter_lines(result) 

133 

134 if lines != result: 

135 with open(filename, "w") as handle: 

136 handle.writelines(result) 

137 return True 

138 return False 

139 

140 

141class PropertiesSortAddon(BaseAddon): 

142 events: ClassVar[set[AddonEvent]] = { 

143 AddonEvent.EVENT_PRE_COMMIT, 

144 } 

145 name = "weblate.properties.sort" 

146 verbose = gettext_lazy("Format the Java properties file") 

147 description = gettext_lazy("Formats and sorts the Java properties file.") 

148 compat: ClassVar[CompatDict] = { 

149 "file_format": {"properties-utf8", "properties", "gwt"}, 

150 } 

151 icon = "sort-alphabetical.svg" 

152 settings_form = PropertiesSortAddonForm 

153 

154 def pre_commit( 

155 self, 

156 translation, 

157 author: str, 

158 store_hash: bool, 

159 activity_log_id: int | None = None, 

160 ) -> None: 

161 case_sensitive = self.instance.configuration.get("case_sensitive", False) 

162 changed = format_file(translation.get_filename(), case_sensitive) 

163 if changed and store_hash: 

164 translation.store_hash()