Coverage for app/venv/lib/python3.14/site-packages/weblate/formats/multi.py: 35%
116 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"""Translate Toolkit based file-format wrappers for multi string support."""
7from __future__ import annotations
9from typing import TYPE_CHECKING, ClassVar
11from django.utils.functional import cached_property
12from django.utils.translation import gettext_lazy
14from weblate.trans.util import get_string
16from .base import TranslationFormat, TranslationUnit
17from .ttkit import CSVUtf8Format
19if TYPE_CHECKING: 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true
20 from translate.storage.base import TranslationStore
21 from translate.storage.base import TranslationUnit as TranslateToolkitUnit
24class MultiUnit(TranslationUnit):
25 units: list[TranslationUnit]
26 parent: MultiFormatMixin
27 empty_unit_ok: ClassVar[bool] = True
29 def __init__(
30 self,
31 parent: MultiFormatMixin,
32 unit: TranslationUnit,
33 template: TranslationUnit | None = None,
34 ) -> None:
35 super().__init__(parent, None, template)
36 self.units = [unit]
38 def merge(self, unit) -> None:
39 self.units.append(unit)
40 self._invalidate_target()
42 @cached_property
43 def locations(self):
44 return ", ".join(unit.locations for unit in self.units)
46 @cached_property
47 def source(self):
48 return get_string(unit.source for unit in self.units)
50 @cached_property
51 def target(self):
52 return get_string(unit.target for unit in self.units)
54 @cached_property
55 def context(self):
56 # Context should be same for all units
57 return self.units[0].context
59 @cached_property
60 def id_hash(self):
61 # Hash should be same for all units
62 return self.units[0].id_hash
64 @cached_property
65 def notes(self):
66 return "\n".join(unit.notes for unit in self.units if unit.notes)
68 def is_translated(self):
69 return any(unit.is_translated() for unit in self.units)
71 def is_fuzzy(self, fallback=False):
72 return any(unit.is_fuzzy(fallback) for unit in self.units)
74 def has_content(self):
75 return any(unit.has_content() for unit in self.units)
77 def is_readonly(self):
78 return any(unit.is_readonly() for unit in self.units)
80 def set_target(self, target: str | list[str]) -> None:
81 """Set translation unit target."""
82 self._invalidate_target()
84 # Mare sure we have a list
85 if isinstance(target, str):
86 target = [target]
88 # Remove any extra units
89 while len(target) < len(self.units):
90 last = self.units.pop()
91 self.parent.store.removeunit(last.unit)
93 # Add missing units
94 while len(target) > len(self.units):
95 new = self.parent.create_unit(self.context, self.units[0].source)
96 self.units.append(
97 self.parent.unit_class(self.parent, new, self.units[0].template)
98 )
99 self.parent.store.addunit(new)
101 for i, value in enumerate(target):
102 self.units[i].set_target(value)
104 def set_state(self, state) -> None:
105 for unit in self.units:
106 unit.set_state(state)
108 @cached_property
109 def flags(self):
110 flags = super().flags
111 for unit in self.units:
112 flags.merge(unit.flags)
113 return flags
115 def has_unit(self) -> bool:
116 return all(unit.has_unit() for unit in self.units)
118 def clone_template(self) -> None:
119 for unit in self.units:
120 if not unit.has_unit():
121 unit.clone_template()
123 def untranslate(self, language) -> None:
124 for unit in self.units:
125 unit.untranslate(language)
128class MultiFormatMixin(TranslationFormat):
129 has_multiple_strings: bool = True
130 units: list[TranslateToolkitUnit]
131 store: TranslationStore
133 def merge_multi(self, iterable):
134 result: dict[int, MultiUnit] = {}
135 for unit in iterable:
136 id_hash = unit.id_hash
137 if id_hash in result:
138 result[id_hash].merge(unit)
139 else:
140 if not isinstance(unit, MultiUnit):
141 unit = MultiUnit(unit.parent, unit, template=unit.template)
142 result[id_hash] = unit
143 return list(result.values())
145 @cached_property
146 def template_units(self):
147 return self.merge_multi(super().template_units)
149 def _get_all_bilingual_units(self):
150 return self.merge_multi(super()._get_all_bilingual_units())
152 def _build_monolingual_unit(self, unit):
153 try:
154 matching = self._template_index[unit.id_hash]
155 except KeyError:
156 return MultiUnit(self, self.unit_class(self, None, unit.units[0].template))
157 matching_units = [unit.template for unit in matching.units]
158 result = MultiUnit(
159 self, self.unit_class(self, matching_units[0], unit.units[0].template)
160 )
161 for extra in matching_units[1:]:
162 result.merge(self.unit_class(self, extra, unit.units[0].template))
163 return result
165 def _get_all_monolingual_units(self):
166 return self.merge_multi(super()._get_all_monolingual_units())
168 def add_unit(self, unit: TranslationUnit) -> None:
169 if isinstance(unit, MultiUnit):
170 for child in unit.units:
171 super().add_unit(child)
172 else:
173 super().add_unit(unit)
176class MultiCSVUtf8Format(MultiFormatMixin, CSVUtf8Format):
177 name = gettext_lazy("Multivalue CSV file (UTF-8)")
178 format_id = "csv-multi-utf-8"