We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
read-only flagged translation strings trigger reused QA check for empty translations
### Describe the issue

In a project using gettext po files I have marked some strings that are for technical reasons included in the translation files but should actually stay unchanged as `read-only`. Now these string also consider the "reused" warning with other read-only strings as they all share the empty translation.

As a workaround I can use of course `ignore-reused` on those strings.


### I already tried

- [X] I've read and searched [the documentation](https://docs.weblate.org/).
- [X] I've searched for similar issues in this repository.

### Steps to reproduce the behavior

1. Add read-only to multiple not yet translated strings

### Expected behavior

Setting to `read-only` should not trigger reused if empty.

### Screenshots

_No response_

### Exception traceback

_No response_

### How do you run Weblate?

self-hosted

### Weblate versions

Weblate 4.18.2

### Weblate deploy checks

_No response_

### Additional context

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `weblate/checks/consistency.py`
Content:
```
1 # Copyright © Michal Čihař <michal@weblate.org>
2 #
3 # SPDX-License-Identifier: GPL-3.0-or-later
4 
5 from __future__ import annotations
6 
7 from functools import reduce
8 
9 from django.db.models import Count, Prefetch, Q
10 from django.utils.translation import gettext, gettext_lazy, ngettext
11 
12 from weblate.checks.base import TargetCheck
13 from weblate.utils.state import STATE_TRANSLATED
14 
15 
16 class PluralsCheck(TargetCheck):
17     """Check for incomplete plural forms."""
18 
19     check_id = "plurals"
20     name = gettext_lazy("Missing plurals")
21     description = gettext_lazy("Some plural forms are untranslated")
22 
23     def should_skip(self, unit):
24         if unit.translation.component.is_multivalue:
25             return True
26         return super().should_skip(unit)
27 
28     def check_target_unit(self, sources, targets, unit):
29         # Is this plural?
30         if len(sources) == 1:
31             return False
32         # Is at least something translated?
33         if targets == len(targets) * [""]:
34             return False
35         # Check for empty translation
36         return "" in targets
37 
38     def check_single(self, source, target, unit):
39         """Target strings are checked in check_target_unit."""
40         return False
41 
42 
43 class SamePluralsCheck(TargetCheck):
44     """Check for same plural forms."""
45 
46     check_id = "same-plurals"
47     name = gettext_lazy("Same plurals")
48     description = gettext_lazy("Some plural forms are translated in the same way")
49 
50     def check_target_unit(self, sources, targets, unit):
51         # Is this plural?
52         if len(sources) == 1 or len(targets) == 1:
53             return False
54         if not targets[0]:
55             return False
56         return len(set(targets)) == 1
57 
58     def check_single(self, source, target, unit):
59         """Target strings are checked in check_target_unit."""
60         return False
61 
62 
63 class ConsistencyCheck(TargetCheck):
64     """Check for inconsistent translations."""
65 
66     check_id = "inconsistent"
67     name = gettext_lazy("Inconsistent")
68     description = gettext_lazy(
69         "This string has more than one translation in this project "
70         "or is untranslated in some components."
71     )
72     ignore_untranslated = False
73     propagates = True
74     batch_project_wide = True
75     skip_suggestions = True
76 
77     def get_propagated_value(self, unit):
78         return unit.target
79 
80     def get_propagated_units(self, unit, target: str | None = None):
81         return unit.same_source_units
82 
83     def check_target_unit(self, sources, targets, unit):
84         component = unit.translation.component
85         if not component.allow_translation_propagation:
86             return False
87 
88         # Use last result if checks are batched
89         if component.batch_checks:
90             return self.handle_batch(unit, component)
91 
92         for other in self.get_propagated_units(unit):
93             if unit.target == other.target:
94                 continue
95             if unit.translated or other.translated:
96                 return True
97         return False
98 
99     def check_single(self, source, target, unit):
100         """Target strings are checked in check_target_unit."""
101         return False
102 
103     def check_component(self, component):
104         from weblate.trans.models import Unit
105 
106         units = Unit.objects.filter(
107             translation__component__project=component.project,
108             translation__component__allow_translation_propagation=True,
109         )
110 
111         # List strings with different targets
112         # Limit this to 100 strings, otherwise the resulting query is way too complex
113         matches = (
114             units.values("id_hash", "translation__language", "translation__plural")
115             .annotate(Count("target", distinct=True))
116             .filter(target__count__gt=1)
117             .order_by("id_hash")[:100]
118         )
119 
120         if not matches:
121             return []
122 
123         return (
124             units.filter(
125                 reduce(
126                     lambda x, y: x
127                     | (
128                         Q(id_hash=y["id_hash"])
129                         & Q(translation__language=y["translation__language"])
130                         & Q(translation__plural=y["translation__plural"])
131                     ),
132                     matches,
133                     Q(),
134                 )
135             )
136             .prefetch()
137             .prefetch_bulk()
138         )
139 
140 
141 class ReusedCheck(TargetCheck):
142     """
143     Check for reused translations.
144 
145     This is skipped for languages with a single plural form as that causes too
146     many false positives, see https://github.com/WeblateOrg/weblate/issues/9450
147     """
148 
149     check_id = "reused"
150     name = gettext_lazy("Reused translation")
151     description = gettext_lazy("Different strings are translated the same.")
152     propagates = True
153     batch_project_wide = True
154     skip_suggestions = True
155 
156     def get_propagated_value(self, unit):
157         return unit.source
158 
159     def get_propagated_units(self, unit, target: str | None = None):
160         from weblate.trans.models import Unit
161 
162         if target is None:
163             return unit.same_target_units
164         return Unit.objects.same_target(unit, target)
165 
166     def should_skip(self, unit):
167         if unit.translation.plural.number <= 1:
168             return True
169         return super().should_skip(unit)
170 
171     def check_target_unit(self, sources, targets, unit):
172         translation = unit.translation
173         component = translation.component
174 
175         # Use last result if checks are batched
176         if component.batch_checks:
177             return self.handle_batch(unit, component)
178 
179         return self.get_propagated_units(unit).exists()
180 
181     def get_description(self, check_obj):
182         other_sources = (
183             self.get_propagated_units(check_obj.unit)
184             .values_list("source", flat=True)
185             .distinct()
186         )
187 
188         return ngettext(
189             "Other source string: %s", "Other source strings: %s", len(other_sources)
190         ) % ", ".join(gettext("“%s”") % source for source in other_sources)
191 
192     def check_single(self, source, target, unit):
193         """Target strings are checked in check_target_unit."""
194         return False
195 
196     def check_component(self, component):
197         from weblate.trans.models import Unit
198 
199         units = Unit.objects.filter(
200             translation__component__project=component.project,
201             translation__component__allow_translation_propagation=True,
202             state__gte=STATE_TRANSLATED,
203         )
204 
205         # List strings with different sources
206         # Limit this to 100 strings, otherwise the resulting query is way too complex
207         matches = (
208             units.values("target__md5", "translation__language", "translation__plural")
209             .annotate(source__count=Count("source", distinct=True))
210             .filter(source__count__gt=1)
211             .order_by("target__md5")[:100]
212         )
213 
214         if not matches:
215             return []
216 
217         return (
218             units.filter(
219                 reduce(
220                     lambda x, y: x
221                     | (
222                         Q(target__md5=y["target__md5"])
223                         & Q(translation__language=y["translation__language"])
224                         & Q(translation__plural=y["translation__plural"])
225                     ),
226                     matches,
227                     Q(),
228                 )
229             )
230             .prefetch()
231             .prefetch_bulk()
232         )
233 
234 
235 class TranslatedCheck(TargetCheck):
236     """Check for inconsistent translations."""
237 
238     check_id = "translated"
239     name = gettext_lazy("Has been translated")
240     description = gettext_lazy("This string has been translated in the past")
241     ignore_untranslated = False
242     skip_suggestions = True
243 
244     def get_description(self, check_obj):
245         unit = check_obj.unit
246         target = self.check_target_unit(unit.source, unit.target, unit)
247         if not target:
248             return super().get_description(check_obj)
249         return gettext('Previous translation was "%s".') % target
250 
251     def should_skip_change(self, change, unit):
252         from weblate.trans.models import Change
253 
254         # Skip automatic translation entries adding needs editing string
255         return (
256             change.action == Change.ACTION_AUTO
257             and change.details.get("state", STATE_TRANSLATED) < STATE_TRANSLATED
258         )
259 
260     @staticmethod
261     def should_break_changes(change):
262         from weblate.trans.models import Change
263 
264         # Stop changes processin on source string change or on
265         # intentional marking as needing edit
266         return change.action in (Change.ACTION_SOURCE_CHANGE, Change.ACTION_MARKED_EDIT)
267 
268     def check_target_unit(self, sources, targets, unit):
269         if unit.translated:
270             return False
271 
272         component = unit.translation.component
273 
274         if component.batch_checks:
275             return self.handle_batch(unit, component)
276 
277         from weblate.trans.models import Change
278 
279         changes = unit.change_set.filter(action__in=Change.ACTIONS_CONTENT).order()
280 
281         for change in changes:
282             if self.should_break_changes(change):
283                 break
284             if self.should_skip_change(change, unit):
285                 continue
286             if change.target and change.target != unit.target:
287                 return change.target
288 
289         return False
290 
291     def check_single(self, source, target, unit):
292         """Target strings are checked in check_target_unit."""
293         return False
294 
295     def get_fixup(self, unit):
296         target = self.check_target_unit(unit.source, unit.target, unit)
297         if not target:
298             return None
299         return [(".*", target, "u")]
300 
301     def check_component(self, component):
302         from weblate.trans.models import Change, Unit
303 
304         units = (
305             Unit.objects.filter(
306                 translation__component=component,
307                 change__action__in=Change.ACTIONS_CONTENT,
308                 state__lt=STATE_TRANSLATED,
309             )
310             .prefetch_related(
311                 Prefetch(
312                     "change_set",
313                     queryset=Change.objects.filter(
314                         action__in=Change.ACTIONS_CONTENT,
315                     ).order(),
316                     to_attr="recent_consistency_changes",
317                 )
318             )
319             .prefetch()
320             .prefetch_bulk()
321         )
322 
323         for unit in units:
324             for change in unit.recent_consistency_changes:
325                 if self.should_break_changes(change):
326                     break
327                 if self.should_skip_change(change, unit):
328                     continue
329                 if change.target:
330                     yield unit
331
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/weblate/checks/consistency.py b/weblate/checks/consistency.py
--- a/weblate/checks/consistency.py
+++ b/weblate/checks/consistency.py
@@ -164,7 +164,7 @@
         return Unit.objects.same_target(unit, target)
 
     def should_skip(self, unit):
-        if unit.translation.plural.number <= 1:
+        if unit.translation.plural.number <= 1 or not any(unit.get_target_plurals()):
             return True
         return super().should_skip(unit)
 
@@ -201,6 +201,7 @@
             translation__component__allow_translation_propagation=True,
             state__gte=STATE_TRANSLATED,
         )
+        units = units.exclude(target="")
 
         # List strings with different sources
         # Limit this to 100 strings, otherwise the resulting query is way too complex

{"golden_diff": "diff --git a/weblate/checks/consistency.py b/weblate/checks/consistency.py\n--- a/weblate/checks/consistency.py\n+++ b/weblate/checks/consistency.py\n@@ -164,7 +164,7 @@\n         return Unit.objects.same_target(unit, target)\n \n     def should_skip(self, unit):\n-        if unit.translation.plural.number <= 1:\n+        if unit.translation.plural.number <= 1 or not any(unit.get_target_plurals()):\n             return True\n         return super().should_skip(unit)\n \n@@ -201,6 +201,7 @@\n             translation__component__allow_translation_propagation=True,\n             state__gte=STATE_TRANSLATED,\n         )\n+        units = units.exclude(target=\"\")\n \n         # List strings with different sources\n         # Limit this to 100 strings, otherwise the resulting query is way too complex\n", "issue": "read-only flagged translation strings trigger reused QA check for empty translations\n### Describe the issue\r\n\r\nIn a project using gettext po files I have marked some strings that are for technical reasons included in the translation files but should actually stay unchanged as `read-only`. Now these string also consider the \"reused\" warning with other read-only strings as they all share the empty translation.\r\n\r\nAs a workaround I can use of course `ignore-reused` on those strings.\r\n\r\n\r\n### I already tried\r\n\r\n- [X] I've read and searched [the documentation](https://docs.weblate.org/).\r\n- [X] I've searched for similar issues in this repository.\r\n\r\n### Steps to reproduce the behavior\r\n\r\n1. Add read-only to multiple not yet translated strings\r\n\r\n### Expected behavior\r\n\r\nSetting to `read-only` should not trigger reused if empty.\r\n\r\n### Screenshots\r\n\r\n_No response_\r\n\r\n### Exception traceback\r\n\r\n_No response_\r\n\r\n### How do you run Weblate?\r\n\r\nself-hosted\r\n\r\n### Weblate versions\r\n\r\nWeblate 4.18.2\r\n\r\n### Weblate deploy checks\r\n\r\n_No response_\r\n\r\n### Additional context\r\n\r\n_No response_\n", "before_files": [{"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import annotations\n\nfrom functools import reduce\n\nfrom django.db.models import Count, Prefetch, Q\nfrom django.utils.translation import gettext, gettext_lazy, ngettext\n\nfrom weblate.checks.base import TargetCheck\nfrom weblate.utils.state import STATE_TRANSLATED\n\n\nclass PluralsCheck(TargetCheck):\n    \"\"\"Check for incomplete plural forms.\"\"\"\n\n    check_id = \"plurals\"\n    name = gettext_lazy(\"Missing plurals\")\n    description = gettext_lazy(\"Some plural forms are untranslated\")\n\n    def should_skip(self, unit):\n        if unit.translation.component.is_multivalue:\n            return True\n        return super().should_skip(unit)\n\n    def check_target_unit(self, sources, targets, unit):\n        # Is this plural?\n        if len(sources) == 1:\n            return False\n        # Is at least something translated?\n        if targets == len(targets) * [\"\"]:\n            return False\n        # Check for empty translation\n        return \"\" in targets\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n\nclass SamePluralsCheck(TargetCheck):\n    \"\"\"Check for same plural forms.\"\"\"\n\n    check_id = \"same-plurals\"\n    name = gettext_lazy(\"Same plurals\")\n    description = gettext_lazy(\"Some plural forms are translated in the same way\")\n\n    def check_target_unit(self, sources, targets, unit):\n        # Is this plural?\n        if len(sources) == 1 or len(targets) == 1:\n            return False\n        if not targets[0]:\n            return False\n        return len(set(targets)) == 1\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n\nclass ConsistencyCheck(TargetCheck):\n    \"\"\"Check for inconsistent translations.\"\"\"\n\n    check_id = \"inconsistent\"\n    name = gettext_lazy(\"Inconsistent\")\n    description = gettext_lazy(\n        \"This string has more than one translation in this project \"\n        \"or is untranslated in some components.\"\n    )\n    ignore_untranslated = False\n    propagates = True\n    batch_project_wide = True\n    skip_suggestions = True\n\n    def get_propagated_value(self, unit):\n        return unit.target\n\n    def get_propagated_units(self, unit, target: str | None = None):\n        return unit.same_source_units\n\n    def check_target_unit(self, sources, targets, unit):\n        component = unit.translation.component\n        if not component.allow_translation_propagation:\n            return False\n\n        # Use last result if checks are batched\n        if component.batch_checks:\n            return self.handle_batch(unit, component)\n\n        for other in self.get_propagated_units(unit):\n            if unit.target == other.target:\n                continue\n            if unit.translated or other.translated:\n                return True\n        return False\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n    def check_component(self, component):\n        from weblate.trans.models import Unit\n\n        units = Unit.objects.filter(\n            translation__component__project=component.project,\n            translation__component__allow_translation_propagation=True,\n        )\n\n        # List strings with different targets\n        # Limit this to 100 strings, otherwise the resulting query is way too complex\n        matches = (\n            units.values(\"id_hash\", \"translation__language\", \"translation__plural\")\n            .annotate(Count(\"target\", distinct=True))\n            .filter(target__count__gt=1)\n            .order_by(\"id_hash\")[:100]\n        )\n\n        if not matches:\n            return []\n\n        return (\n            units.filter(\n                reduce(\n                    lambda x, y: x\n                    | (\n                        Q(id_hash=y[\"id_hash\"])\n                        & Q(translation__language=y[\"translation__language\"])\n                        & Q(translation__plural=y[\"translation__plural\"])\n                    ),\n                    matches,\n                    Q(),\n                )\n            )\n            .prefetch()\n            .prefetch_bulk()\n        )\n\n\nclass ReusedCheck(TargetCheck):\n    \"\"\"\n    Check for reused translations.\n\n    This is skipped for languages with a single plural form as that causes too\n    many false positives, see https://github.com/WeblateOrg/weblate/issues/9450\n    \"\"\"\n\n    check_id = \"reused\"\n    name = gettext_lazy(\"Reused translation\")\n    description = gettext_lazy(\"Different strings are translated the same.\")\n    propagates = True\n    batch_project_wide = True\n    skip_suggestions = True\n\n    def get_propagated_value(self, unit):\n        return unit.source\n\n    def get_propagated_units(self, unit, target: str | None = None):\n        from weblate.trans.models import Unit\n\n        if target is None:\n            return unit.same_target_units\n        return Unit.objects.same_target(unit, target)\n\n    def should_skip(self, unit):\n        if unit.translation.plural.number <= 1:\n            return True\n        return super().should_skip(unit)\n\n    def check_target_unit(self, sources, targets, unit):\n        translation = unit.translation\n        component = translation.component\n\n        # Use last result if checks are batched\n        if component.batch_checks:\n            return self.handle_batch(unit, component)\n\n        return self.get_propagated_units(unit).exists()\n\n    def get_description(self, check_obj):\n        other_sources = (\n            self.get_propagated_units(check_obj.unit)\n            .values_list(\"source\", flat=True)\n            .distinct()\n        )\n\n        return ngettext(\n            \"Other source string: %s\", \"Other source strings: %s\", len(other_sources)\n        ) % \", \".join(gettext(\"\u201c%s\u201d\") % source for source in other_sources)\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n    def check_component(self, component):\n        from weblate.trans.models import Unit\n\n        units = Unit.objects.filter(\n            translation__component__project=component.project,\n            translation__component__allow_translation_propagation=True,\n            state__gte=STATE_TRANSLATED,\n        )\n\n        # List strings with different sources\n        # Limit this to 100 strings, otherwise the resulting query is way too complex\n        matches = (\n            units.values(\"target__md5\", \"translation__language\", \"translation__plural\")\n            .annotate(source__count=Count(\"source\", distinct=True))\n            .filter(source__count__gt=1)\n            .order_by(\"target__md5\")[:100]\n        )\n\n        if not matches:\n            return []\n\n        return (\n            units.filter(\n                reduce(\n                    lambda x, y: x\n                    | (\n                        Q(target__md5=y[\"target__md5\"])\n                        & Q(translation__language=y[\"translation__language\"])\n                        & Q(translation__plural=y[\"translation__plural\"])\n                    ),\n                    matches,\n                    Q(),\n                )\n            )\n            .prefetch()\n            .prefetch_bulk()\n        )\n\n\nclass TranslatedCheck(TargetCheck):\n    \"\"\"Check for inconsistent translations.\"\"\"\n\n    check_id = \"translated\"\n    name = gettext_lazy(\"Has been translated\")\n    description = gettext_lazy(\"This string has been translated in the past\")\n    ignore_untranslated = False\n    skip_suggestions = True\n\n    def get_description(self, check_obj):\n        unit = check_obj.unit\n        target = self.check_target_unit(unit.source, unit.target, unit)\n        if not target:\n            return super().get_description(check_obj)\n        return gettext('Previous translation was \"%s\".') % target\n\n    def should_skip_change(self, change, unit):\n        from weblate.trans.models import Change\n\n        # Skip automatic translation entries adding needs editing string\n        return (\n            change.action == Change.ACTION_AUTO\n            and change.details.get(\"state\", STATE_TRANSLATED) < STATE_TRANSLATED\n        )\n\n    @staticmethod\n    def should_break_changes(change):\n        from weblate.trans.models import Change\n\n        # Stop changes processin on source string change or on\n        # intentional marking as needing edit\n        return change.action in (Change.ACTION_SOURCE_CHANGE, Change.ACTION_MARKED_EDIT)\n\n    def check_target_unit(self, sources, targets, unit):\n        if unit.translated:\n            return False\n\n        component = unit.translation.component\n\n        if component.batch_checks:\n            return self.handle_batch(unit, component)\n\n        from weblate.trans.models import Change\n\n        changes = unit.change_set.filter(action__in=Change.ACTIONS_CONTENT).order()\n\n        for change in changes:\n            if self.should_break_changes(change):\n                break\n            if self.should_skip_change(change, unit):\n                continue\n            if change.target and change.target != unit.target:\n                return change.target\n\n        return False\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n    def get_fixup(self, unit):\n        target = self.check_target_unit(unit.source, unit.target, unit)\n        if not target:\n            return None\n        return [(\".*\", target, \"u\")]\n\n    def check_component(self, component):\n        from weblate.trans.models import Change, Unit\n\n        units = (\n            Unit.objects.filter(\n                translation__component=component,\n                change__action__in=Change.ACTIONS_CONTENT,\n                state__lt=STATE_TRANSLATED,\n            )\n            .prefetch_related(\n                Prefetch(\n                    \"change_set\",\n                    queryset=Change.objects.filter(\n                        action__in=Change.ACTIONS_CONTENT,\n                    ).order(),\n                    to_attr=\"recent_consistency_changes\",\n                )\n            )\n            .prefetch()\n            .prefetch_bulk()\n        )\n\n        for unit in units:\n            for change in unit.recent_consistency_changes:\n                if self.should_break_changes(change):\n                    break\n                if self.should_skip_change(change, unit):\n                    continue\n                if change.target:\n                    yield unit\n", "path": "weblate/checks/consistency.py"}], "after_files": [{"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import annotations\n\nfrom functools import reduce\n\nfrom django.db.models import Count, Prefetch, Q\nfrom django.utils.translation import gettext, gettext_lazy, ngettext\n\nfrom weblate.checks.base import TargetCheck\nfrom weblate.utils.state import STATE_TRANSLATED\n\n\nclass PluralsCheck(TargetCheck):\n    \"\"\"Check for incomplete plural forms.\"\"\"\n\n    check_id = \"plurals\"\n    name = gettext_lazy(\"Missing plurals\")\n    description = gettext_lazy(\"Some plural forms are untranslated\")\n\n    def should_skip(self, unit):\n        if unit.translation.component.is_multivalue:\n            return True\n        return super().should_skip(unit)\n\n    def check_target_unit(self, sources, targets, unit):\n        # Is this plural?\n        if len(sources) == 1:\n            return False\n        # Is at least something translated?\n        if targets == len(targets) * [\"\"]:\n            return False\n        # Check for empty translation\n        return \"\" in targets\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n\nclass SamePluralsCheck(TargetCheck):\n    \"\"\"Check for same plural forms.\"\"\"\n\n    check_id = \"same-plurals\"\n    name = gettext_lazy(\"Same plurals\")\n    description = gettext_lazy(\"Some plural forms are translated in the same way\")\n\n    def check_target_unit(self, sources, targets, unit):\n        # Is this plural?\n        if len(sources) == 1 or len(targets) == 1:\n            return False\n        if not targets[0]:\n            return False\n        return len(set(targets)) == 1\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n\nclass ConsistencyCheck(TargetCheck):\n    \"\"\"Check for inconsistent translations.\"\"\"\n\n    check_id = \"inconsistent\"\n    name = gettext_lazy(\"Inconsistent\")\n    description = gettext_lazy(\n        \"This string has more than one translation in this project \"\n        \"or is untranslated in some components.\"\n    )\n    ignore_untranslated = False\n    propagates = True\n    batch_project_wide = True\n    skip_suggestions = True\n\n    def get_propagated_value(self, unit):\n        return unit.target\n\n    def get_propagated_units(self, unit, target: str | None = None):\n        return unit.same_source_units\n\n    def check_target_unit(self, sources, targets, unit):\n        component = unit.translation.component\n        if not component.allow_translation_propagation:\n            return False\n\n        # Use last result if checks are batched\n        if component.batch_checks:\n            return self.handle_batch(unit, component)\n\n        for other in self.get_propagated_units(unit):\n            if unit.target == other.target:\n                continue\n            if unit.translated or other.translated:\n                return True\n        return False\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n    def check_component(self, component):\n        from weblate.trans.models import Unit\n\n        units = Unit.objects.filter(\n            translation__component__project=component.project,\n            translation__component__allow_translation_propagation=True,\n        )\n\n        # List strings with different targets\n        # Limit this to 100 strings, otherwise the resulting query is way too complex\n        matches = (\n            units.values(\"id_hash\", \"translation__language\", \"translation__plural\")\n            .annotate(Count(\"target\", distinct=True))\n            .filter(target__count__gt=1)\n            .order_by(\"id_hash\")[:100]\n        )\n\n        if not matches:\n            return []\n\n        return (\n            units.filter(\n                reduce(\n                    lambda x, y: x\n                    | (\n                        Q(id_hash=y[\"id_hash\"])\n                        & Q(translation__language=y[\"translation__language\"])\n                        & Q(translation__plural=y[\"translation__plural\"])\n                    ),\n                    matches,\n                    Q(),\n                )\n            )\n            .prefetch()\n            .prefetch_bulk()\n        )\n\n\nclass ReusedCheck(TargetCheck):\n    \"\"\"\n    Check for reused translations.\n\n    This is skipped for languages with a single plural form as that causes too\n    many false positives, see https://github.com/WeblateOrg/weblate/issues/9450\n    \"\"\"\n\n    check_id = \"reused\"\n    name = gettext_lazy(\"Reused translation\")\n    description = gettext_lazy(\"Different strings are translated the same.\")\n    propagates = True\n    batch_project_wide = True\n    skip_suggestions = True\n\n    def get_propagated_value(self, unit):\n        return unit.source\n\n    def get_propagated_units(self, unit, target: str | None = None):\n        from weblate.trans.models import Unit\n\n        if target is None:\n            return unit.same_target_units\n        return Unit.objects.same_target(unit, target)\n\n    def should_skip(self, unit):\n        if unit.translation.plural.number <= 1 or not any(unit.get_target_plurals()):\n            return True\n        return super().should_skip(unit)\n\n    def check_target_unit(self, sources, targets, unit):\n        translation = unit.translation\n        component = translation.component\n\n        # Use last result if checks are batched\n        if component.batch_checks:\n            return self.handle_batch(unit, component)\n\n        return self.get_propagated_units(unit).exists()\n\n    def get_description(self, check_obj):\n        other_sources = (\n            self.get_propagated_units(check_obj.unit)\n            .values_list(\"source\", flat=True)\n            .distinct()\n        )\n\n        return ngettext(\n            \"Other source string: %s\", \"Other source strings: %s\", len(other_sources)\n        ) % \", \".join(gettext(\"\u201c%s\u201d\") % source for source in other_sources)\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n    def check_component(self, component):\n        from weblate.trans.models import Unit\n\n        units = Unit.objects.filter(\n            translation__component__project=component.project,\n            translation__component__allow_translation_propagation=True,\n            state__gte=STATE_TRANSLATED,\n        )\n        units = units.exclude(target=\"\")\n\n        # List strings with different sources\n        # Limit this to 100 strings, otherwise the resulting query is way too complex\n        matches = (\n            units.values(\"target__md5\", \"translation__language\", \"translation__plural\")\n            .annotate(source__count=Count(\"source\", distinct=True))\n            .filter(source__count__gt=1)\n            .order_by(\"target__md5\")[:100]\n        )\n\n        if not matches:\n            return []\n\n        return (\n            units.filter(\n                reduce(\n                    lambda x, y: x\n                    | (\n                        Q(target__md5=y[\"target__md5\"])\n                        & Q(translation__language=y[\"translation__language\"])\n                        & Q(translation__plural=y[\"translation__plural\"])\n                    ),\n                    matches,\n                    Q(),\n                )\n            )\n            .prefetch()\n            .prefetch_bulk()\n        )\n\n\nclass TranslatedCheck(TargetCheck):\n    \"\"\"Check for inconsistent translations.\"\"\"\n\n    check_id = \"translated\"\n    name = gettext_lazy(\"Has been translated\")\n    description = gettext_lazy(\"This string has been translated in the past\")\n    ignore_untranslated = False\n    skip_suggestions = True\n\n    def get_description(self, check_obj):\n        unit = check_obj.unit\n        target = self.check_target_unit(unit.source, unit.target, unit)\n        if not target:\n            return super().get_description(check_obj)\n        return gettext('Previous translation was \"%s\".') % target\n\n    def should_skip_change(self, change, unit):\n        from weblate.trans.models import Change\n\n        # Skip automatic translation entries adding needs editing string\n        return (\n            change.action == Change.ACTION_AUTO\n            and change.details.get(\"state\", STATE_TRANSLATED) < STATE_TRANSLATED\n        )\n\n    @staticmethod\n    def should_break_changes(change):\n        from weblate.trans.models import Change\n\n        # Stop changes processin on source string change or on\n        # intentional marking as needing edit\n        return change.action in (Change.ACTION_SOURCE_CHANGE, Change.ACTION_MARKED_EDIT)\n\n    def check_target_unit(self, sources, targets, unit):\n        if unit.translated:\n            return False\n\n        component = unit.translation.component\n\n        if component.batch_checks:\n            return self.handle_batch(unit, component)\n\n        from weblate.trans.models import Change\n\n        changes = unit.change_set.filter(action__in=Change.ACTIONS_CONTENT).order()\n\n        for change in changes:\n            if self.should_break_changes(change):\n                break\n            if self.should_skip_change(change, unit):\n                continue\n            if change.target and change.target != unit.target:\n                return change.target\n\n        return False\n\n    def check_single(self, source, target, unit):\n        \"\"\"Target strings are checked in check_target_unit.\"\"\"\n        return False\n\n    def get_fixup(self, unit):\n        target = self.check_target_unit(unit.source, unit.target, unit)\n        if not target:\n            return None\n        return [(\".*\", target, \"u\")]\n\n    def check_component(self, component):\n        from weblate.trans.models import Change, Unit\n\n        units = (\n            Unit.objects.filter(\n                translation__component=component,\n                change__action__in=Change.ACTIONS_CONTENT,\n                state__lt=STATE_TRANSLATED,\n            )\n            .prefetch_related(\n                Prefetch(\n                    \"change_set\",\n                    queryset=Change.objects.filter(\n                        action__in=Change.ACTIONS_CONTENT,\n                    ).order(),\n                    to_attr=\"recent_consistency_changes\",\n                )\n            )\n            .prefetch()\n            .prefetch_bulk()\n        )\n\n        for unit in units:\n            for change in unit.recent_consistency_changes:\n                if self.should_break_changes(change):\n                    break\n                if self.should_skip_change(change, unit):\n                    continue\n                if change.target:\n                    yield unit\n", "path": "weblate/checks/consistency.py"}]}