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

60 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"""Automatic detection of file format.""" 

6 

7from __future__ import annotations 

8 

9import os.path 

10from fnmatch import fnmatch 

11from typing import TYPE_CHECKING, Any 

12 

13from translate.storage import factory 

14 

15from weblate.formats.helpers import NamedBytesIO 

16from weblate.formats.models import FILE_FORMATS 

17from weblate.formats.ttkit import TTKitFormat 

18 

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

20 from collections.abc import Generator 

21 

22 from weblate.formats.base import TranslationFormat 

23 

24 

25def detect_filename(filename: str) -> type[TranslationFormat] | None: 

26 """Filename based format autodetection.""" 

27 name = os.path.basename(filename) 

28 for pattern, storeclass in FILE_FORMATS.autoload: 

29 if fnmatch(name, pattern): 

30 return storeclass 

31 return None 

32 

33 

34def formats_iter( 

35 filename: str, original_format: type[TranslationFormat] | None 

36) -> Generator[type[TranslationFormat], None, None]: 

37 # Detect based on the extension 

38 detected_format = detect_filename(filename) 

39 if detected_format is not None and detected_format != original_format: 

40 # Insert detected filename into most probable location. In case the extension 

41 # matches original, insert it after that as it is more likely that the upload 

42 # is in the original format (for example if component is monolingual PO file, 

43 # the uploaded PO file is more likely to be monolingual as well). 

44 if ( 

45 original_format is not None 

46 and detected_format.extension() == original_format.extension() 

47 ): 

48 yield original_format 

49 yield detected_format 

50 else: 

51 yield detected_format 

52 if original_format is not None: 

53 yield original_format 

54 elif original_format is not None: 

55 yield original_format 

56 

57 # Provide fallback to bilingual class in case using monolingual 

58 if ( 

59 original_format is not None 

60 and original_format.bilingual_class 

61 and original_format.bilingual_class != detected_format 

62 ): 

63 yield original_format.bilingual_class 

64 

65 # Fallback to translate-toolkit autodetection 

66 yield AutodetectFormat 

67 

68 

69def params_iter( 

70 file_format: type[TranslationFormat], 

71 template_store: TranslationFormat | None, 

72 is_template: bool = False, 

73) -> Generator[tuple[dict[str, Any], bool], None, None]: 

74 if file_format.monolingual in {True, None} and (template_store or is_template): 

75 yield {"template_store": template_store, "is_template": is_template}, True 

76 

77 if file_format.monolingual in {False, None}: 

78 yield {}, False 

79 

80 

81def try_load( 

82 filename: str, 

83 content: bytes, 

84 original_format: type[TranslationFormat] | None, 

85 template_store: TranslationFormat | None, 

86 language_code: str | None = None, 

87 source_language: str | None = None, 

88 is_template: bool = False, 

89 file_format_params: dict[str, Any] | None = None, 

90) -> TranslationFormat: 

91 """Try to load file by guessing type.""" 

92 failure = None 

93 for file_format in formats_iter(filename, original_format): 

94 for kwargs, validate in params_iter(file_format, template_store, is_template): 

95 handle = NamedBytesIO(filename, content) 

96 try: 

97 result = file_format( 

98 handle, 

99 language_code=language_code, 

100 source_language=language_code 

101 if kwargs.get("is_template", False) 

102 else source_language, 

103 file_format_params=file_format_params, 

104 **kwargs, 

105 ) 

106 result.check_valid() 

107 except Exception as error: 

108 if failure is None: 

109 failure = error 

110 else: 

111 # Skip if there is untranslated unit 

112 # this can easily happen when importing bilingual 

113 # storage which can be monolingual as well 

114 if not validate or any(result.iterate_merge("")): 

115 return result 

116 

117 if failure is None: 

118 msg = "Could not load file." 

119 raise ValueError(msg) 

120 raise failure 

121 

122 

123class AutodetectFormat(TTKitFormat): 

124 """ 

125 Automatic detection based on translate-toolkit logic. 

126 

127 This is last fallback when uploaded file was not correctly parsed before. 

128 """ 

129 

130 @classmethod 

131 def parse_store(cls, storefile): 

132 """Directly loads using translate-toolkit.""" 

133 return factory.getobject(storefile) 

134 

135 @classmethod 

136 def get_class(cls) -> None: 

137 return None