Repository: WeblateOrg/weblate

Issue:
Got MonolingualTranslation alert when using bilingual translation files
### Describe the problem
We are using CSV format bilingual translation files while weblate keep alerting that our components are monolingual.
The error message is "The translation component looks monolingual, but it is not set up to be. Please add a monolingual base language file in the component settings."

After tracing the source code, we found that the code causing this is at line 416~423 of weblate/trans/models/alert.py (checked on revision ae8b4db)

```python
        if translation:
            allunits = translation.unit_set
        else:
            allunits = component.source_translation.unit_set

        source_space = allunits.filter(source__contains=" ")
        target_space = allunits.filter(
            state__gte=STATE_TRANSLATED, target__contains=" "
        )
        return (
            not component.is_glossary
            and not component.template
            and allunits.count() > 3
            and not source_space.exists()
            and target_space.exists()
        )
```
I can't figure out why weblate needs to check `source_space` and `target_space` here to determine if the component is monolingual. 
Our source language uses CJK characters and do not use so-called `spaces`. However, our target language, English, does use spaces. So as a result, there exist `target_space` units and exists no `source_space` units, which leads to the MonolingualTranslation alert. But it doesn't mean the component is  monolingual.

### Describe the solution you would like
We can handle this problem by commenting out this check. We share this issue here in case someone meets the same problem.

### I already tried

- [X] I've read and searched [the documentation](https://docs.weblate.org/).
- [X] I've searched for similar filed issues in this repository.

### Steps to reproduce the behavior

1. Upload bilingual translation file whose source uses CJK characters and target language is English.


Discussion:
This indeed assumes that source is using whitespace to separate words, the code needs to be adjusted.

Resolving patch:
diff --git a/weblate/lang/data.py b/weblate/lang/data.py
--- a/weblate/lang/data.py
+++ b/weblate/lang/data.py
@@ -9,6 +9,15 @@
 
 NO_CODE_LANGUAGES = {lang[0] for lang in languages.LANGUAGES}
 
+NO_SPACE_LANGUAGES = {
+    "zh",
+    "ja",
+    "th",
+    "km",
+    "lo",
+    "my",
+}
+
 UNDERSCORE_EXCEPTIONS = {
     "nb_NO",
     "zh_Hant",
diff --git a/weblate/lang/models.py b/weblate/lang/models.py
--- a/weblate/lang/models.py
+++ b/weblate/lang/models.py
@@ -648,6 +648,9 @@ def base_code(self) -> str:
     def uses_ngram(self) -> bool:
         return is_ngram_code(self.base_code)
 
+    def uses_whitespace(self) -> bool:
+        return self.base_code not in data.NO_SPACE_LANGUAGES
+
     @cached_property
     def plural(self):
         if self.plural_set.all()._result_cache is not None:
diff --git a/weblate/trans/models/alert.py b/weblate/trans/models/alert.py
--- a/weblate/trans/models/alert.py
+++ b/weblate/trans/models/alert.py
@@ -27,7 +27,7 @@
 if TYPE_CHECKING:
     from django_stubs_ext import StrOrPromise
 
-    from weblate.trans.models import Component
+    from weblate.trans.models import Component, Translation
 
 ALERTS = {}
 ALERTS_IMPORT = set()
@@ -406,28 +406,34 @@ class MonolingualTranslation(BaseAlert):
 
     @staticmethod
     def check_component(component: Component) -> bool | None | dict:
-        # Pick random translation with translated strings except source one
-        translation = (
-            component.translation_set.filter(unit__state__gte=STATE_TRANSLATED)
-            .exclude(language_id=component.source_language_id)
-            .first()
-        )
+        if (
+            component.is_glossary
+            or not component.source_language.uses_whitespace()
+            or component.template
+        ):
+            return False
 
-        if translation:
-            allunits = translation.unit_set
-        else:
-            allunits = component.source_translation.unit_set
+        # Pick translation with translated strings except source one
+        translation: None | Translation = None
+        for current in component.translation_set.filter(
+            unit__state__gte=STATE_TRANSLATED
+        ).exclude(language_id=component.source_language_id):
+            if not current.language.uses_whitespace():
+                continue
+            translation = current
+
+        # Bail out if there is no suitable translation
+        if translation is None:
+            return False
+
+        allunits = translation.unit_set
 
         source_space = allunits.filter(source__contains=" ")
         target_space = allunits.filter(
             state__gte=STATE_TRANSLATED, target__contains=" "
         )
         return (
-            not component.is_glossary
-            and not component.template
-            and allunits.count() > 3
-            and not source_space.exists()
-            and target_space.exists()
+            allunits.count() > 3 and not source_space.exists() and target_space.exists()
         )
 
 


Test patch: