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
« 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
5"""Automatic detection of file format."""
7from __future__ import annotations
9import os.path
10from fnmatch import fnmatch
11from typing import TYPE_CHECKING, Any
13from translate.storage import factory
15from weblate.formats.helpers import NamedBytesIO
16from weblate.formats.models import FILE_FORMATS
17from weblate.formats.ttkit import TTKitFormat
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
22 from weblate.formats.base import TranslationFormat
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
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
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
65 # Fallback to translate-toolkit autodetection
66 yield AutodetectFormat
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
77 if file_format.monolingual in {False, None}:
78 yield {}, False
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
117 if failure is None:
118 msg = "Could not load file."
119 raise ValueError(msg)
120 raise failure
123class AutodetectFormat(TTKitFormat):
124 """
125 Automatic detection based on translate-toolkit logic.
127 This is last fallback when uploaded file was not correctly parsed before.
128 """
130 @classmethod
131 def parse_store(cls, storefile):
132 """Directly loads using translate-toolkit."""
133 return factory.getobject(storefile)
135 @classmethod
136 def get_class(cls) -> None:
137 return None